Compare commits

..

No commits in common. "master" and "mapping_problem" have entirely different histories.

17 changed files with 21 additions and 460 deletions

View file

@ -2,7 +2,7 @@
## Tooling
- Python 3.6
- Python 3.5
- [MyPy](http://www.mypy-lang.org/ ) für statische Typchecks
- [Pandoc](https://pandoc.org/ ) für die Dokumentation
- Python Module: siehe [requirements.txt](https://pip.pypa.io/en/latest/user_guide/#requirements-files )

View file

@ -1,23 +0,0 @@
README
-----
Für die Ausführung des Algorithmus wird Python 3 (empfohlene Version: 3.6.1) benötigt.
Die Packages, die zusätzlich gebraucht werden, können der requirements.txt entnommen werden.
(Installation kann hier einzeln oder über den Befehl: python -m pip install -r requirements.txt)
Zur Ausführung bitte im Terminal in den Ordner src gehen und dort das Skript main.py starten.
Parameter, die hierbei möglich sind:
-h zeigt alle Optionen an
-p aktiviert die Ausgabe über den Plotter als Diagramm
-l wird benötigt falls die Eingabe eine Liste von Problemen ist (d.h. für jobshop1.txt)
-i Index des Problems in der Liste (nur relevant bei -l)
-t setzt die Starttemperatur des Simulated Annealings
-s setzt die maximalen Umformungsschritte pro Generierung einer neuen Lösung
-a setzt die Wahrscheinlichkeit, pro Umformungsschritt auch eine Lösung zu akzeptieren, obwohl
noch nicht die maximalen Umformungsschritte erreicht sind
-t -s und -a müssen nicht alle gesetzt sein, dann wird der jeweilige Defaultwert verwendet
Defaultwerte: max_temp = 300, max_steps = 250, accept_prob = 0.01
Beispielaufruf:
python .\main.py -p -l -i 2 -t 50 ..\inputdata\jobshop1.txt

36
doc.md
View file

@ -1,36 +0,0 @@
## scheduling problem defined by:
1. $m$ specialized machines
2. tasks $\tau$ of the form $(e, i)$ with $t \in \mathbb{N}$ the execution time and $i \in \{1,2,\dots,m\}$ the machine the task has to run on
3. $n$ jobs $T_k$ with $\forall T_k:$ linear order of tasks, with $k \in \{1,2,\dots,n\}$
4. Additionally, a multiset $\Omega$ of arbitrary but fixed size that contains wait states $\omega := (1, i)$ with $i \in \{1,2,\dots,m\}$ the blocked machine.
The goal is to find the fastest feasible schedule $\sigma_{min}$.
## evaluative function
- minimize the execution time of $\sigma$
- upper bound: largest processing time first
- lower bound: max sum of execution times on one machine
## solutions
- list of tuples $(t, \tau)$ with $t \in \mathbb{N}$ the scheduled begin of $\tau$
## operations
- $\operatorname{ins}(\omega, t)$: block a machine at time $t$ for $w$ time steps.
- $\operatorname{xchg}(\tau_1,\tau_2)$: exchange the position of two tasks.
Both operations require that the start times are recomputed.
## neighbourhood of solution
- $\operatorname{neighbours}(\sigma) = \{x \in \Sigma | \delta(\sigma, x) \leq n\}$ with $\Sigma$ the set of all feasible schedules.
- $\delta$: $\delta ( \sigma )=0$, $\delta ( \operatorname{op}(x)) = \delta (x) + 1$ (ass. ins has the same penalty xchg has), $x$ either op($y$) or $\sigma$
## constraints
- only schedule new $\tau$ if another $\tau$ is finished
- only schedule $\tau \in T_k$ that has no unscheduled predecessor in $T_k$
- only one task on a machine any given time
## implementation in Python
- translate problem into list of jobs, jobs into lists of tasks, ie problem = [$T_0, T_1,\dots,T_{k-1}$], $T_i$ = [$\tau_1,\tau_2,\dots$]
- address tasks based on their indices, ie [0][1] is the second task of the first job.
- compute only one possible next solution, rate, drop/accept. $\delta$ is computed iteratively during generation

View file

@ -1,4 +0,0 @@
3 3
0 4 1 6 2 1
1 3 0 13 2 4
1 2 2 5 0 3

View file

@ -26,7 +26,7 @@
- $S = \left\{(o_j,t) | o_j \in O \cup \left\{w_n | n \in \mathbb{N} \wedge w_n \text{ v.d.F. } (1, m) \right\} \wedge o_j \text{ v.d.F. } (d, m, j) \wedge t \in T \forall o \in O : \exists (o,t) \in S\right\}$
- indirekt lässt sich durch laufende Operation und Zeitpunkt auch Belegung einer Maschine zu einem Zeitpunkt ermitteln
- Optimierung: sparse speichern
1. Liste von (T, $o_j$) mit $T \in \mathbb{N}$ (Time), $o_j \in O$ (Tasks), j bezeichnet den Job
1. Liste von (T, $o_j$) mit $T \in \mathbb{N}$ (Time), $o_j \in O$ (Tasks)
- Operationen:
- Vertauschen von 2 Jobs auf einer Maschine, selbstinvers
- Verzögern von Operationen (keine expliziten Wartezustände nötig)

View file

@ -1,5 +1,2 @@
mypy
arpeggio
matplotlib
numpy
tkinter
Arpeggio

View file

@ -1,3 +0,0 @@
with import <nixpkgs> {};
(python3.withPackages (ps: [ps.numpy (ps.matplotlib.override {enableQt=true;}) ps.mypy ps.arpeggio])).env

View file

@ -1,198 +0,0 @@
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)
#return 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

View file

View file

@ -1,32 +0,0 @@
from matplotlib import pyplot as plt
from matplotlib import colors
from matplotlib import patches
import numpy as np
from SchedulingAlgorithms.simanneal import rate
import random
def create_plot(problem, solution):
end = rate(solution)
with plt.xkcd():
fig,ax = plt.subplots()
col = colors.XKCD_COLORS
del col['xkcd:white']
colorlist = list(col.values())
random.shuffle(colorlist)
for m in range(0, problem.machines):
mach_ops = [ x for x in solution if problem.problem_data[x[1][0]][x[1][1]][1] == m ]
xranges = [ (x[0], problem.problem_data[x[1][0]][x[1][1]][0]) for x in mach_ops ]
ax.broken_barh(xranges, ((problem.machines - m)*10, 9),
facecolors=[colorlist[x[1][0]] for x in mach_ops])
ax.set_ylim(5, 5 + (problem.machines+1)*10)
ax.set_xlim(0, 1.25 * end)
ax.set_yticks([15 + m * 10 for m in range(0, problem.machines)])
ax.set_yticklabels([ problem.machines - 1 - m for m in range(0, problem.machines)] )
handlecolors = colorlist[0:problem.jobs]
handles = [ patches.Patch(color = handlecolors[j], label = "Job "+str(j)) for j in range(0,problem.jobs) ]
labels = ["Job "+str(j) for j in range(0,problem.jobs)]
ax.legend(handles, labels)
plt.show()

View file

@ -6,33 +6,33 @@ from collections.abc import Mapping
__all__ = ["js1_style", "js2_style"]
grammar = """
// starting point for jobshop1 input file
# starting point for jobshop1 input file
job_shop1 = skip_preface
// eat away lines of preface, until first problem_instance is
// encountered; then the list of instances start
# eat away lines of preface, until first problem_instance is
# encountered; then the list of instances start
skip_preface = (!problem_instance r"[^\n]+" skip_preface) / (eol skip_preface) / instance_list
instance_list = problem_instance (sep_line trim_ws eol problem_instance eol?)* eof_sep
problem_instance = trim_ws "instance" ' ' instance_name trim_ws eol trim_ws eol sep_line description eol problem_data
description = r"[^\n]*"
instance_name = r"\w+"
sep_line = trim_ws plus_line trim_ws eol
// lines out of multiple + signs
# lines out of multiple + signs
plus_line = r"\+\+\++"
// EOF is a builtin rule matching end of file
# EOF is a builtin rule matching end of file
eof_sep = trim_ws plus_line " EOF " plus_line trim_ws eol* EOF
// entry point for jobshop2 input files
# entry point for jobshop2 input files
job_shop2 = problem_data EOF
problem_data = trim_ws num_jobs ' ' num_machines eol job_data+
// used for skipping arbitrary number of non-breaking whitespace
# used for skipping arbitrary number of non-breaking whitespace
trim_ws = r'[ \t]*'
// git may change line-endings on windows, so we have to match on both
# git may change line-endings on windows, so we have to match on both
eol = "\n" / "\r\n"
nonneg_num = r'\d+'
num_jobs = nonneg_num
num_machines = nonneg_num
machine = nonneg_num
duration = nonneg_num
// task data for 1 job
# task data for 1 job
job_data = ' '* machine ' '+ duration (' '+ machine ' '+ duration)* trim_ws eol
"""

View file

@ -1,10 +0,0 @@
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

View file

@ -1,36 +0,0 @@
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

View file

@ -1,10 +1,7 @@
import Parser.js1_style as p
#import Parser.js2_style as p
from SchedulingAlgorithms import simanneal as sim
from Output import output as o
INSTANCES = [(5, 5)]
TASKS = [[(1, 21), (0, 53), (4, 95), (3, 55), (2, 35)],
[(0, 21), (3, 52), (4, 16), (2, 26), (1, 71)],
[(3, 39), (4, 98), (1, 42), (2, 31), (0, 12)],
[(1, 77), (0, 55), (4, 79), (2, 66), (3, 77)],
[(0, 83), (3, 34), (2, 64), (1, 19), (4, 37)]]
problem = p.parse_file("../inputdata/jobshop1.txt")[0]
#problem = p.parse_file("../inputdata/sample")
sim.init(problem)
solution = sim.anneal()
o.create_plot(problem, solution)

View file

@ -1,97 +1,6 @@
#! /usr/bin/env python
def main() -> None:
pass
import sys
import getopt
from SchedulingAlgorithms import simanneal as sim
from Output import output as o
def usage():
s= """
Command line options:
-h show this help
-p activate pretty output (requires tkinter)
-l assume that a file contains multiple problems, default is only 1
-i index of the problem you want solved. has no effect without l
-t set parameter max_temp of simulated annealing
-s set parameter max_steps of simulated annealing
-a set parameter accept_prob of simulated annealing
Invocation:
python [-hlp] file
"""
return s
def main():
js1 = False
plot = False
try:
opts, args = getopt.getopt(sys.argv[1:], 'hpli:t:s:a:')
except getopt.GetoptError as err:
print(err)
sys.exit()
if ('-h', '') in opts:
print(usage())
if ('-p', '') in opts:
print("Plotting enabled.")
from Output import output as o
plot = True
if('-l', '') in opts:
js1 = True
idx = [int(x[1]) for x in opts if x[0]=='-i']
idx = idx[0] if idx else -1
max_temp = [int(x[1]) for x in opts if x[0]=='-t']
max_temp = max_temp[0] if max_temp else -1
max_steps = [int(x[1]) for x in opts if x[0]=='-s']
max_steps = max_steps[0] if max_steps else -1
accept_prob = [int(x[1]) for x in opts if x[0]=='-a']
accept_prob = accept_prob[0] if accept_prob else -1
if not args:
print("No file given.")
sys.exit()
else:
infile = args[0]
if js1:
from Parser import js1_style as parser
else:
from Parser import js2_style as parser
print("Parsing file: " + infile)
problem = parser.parse_file(infile)
if js1:
print("File contains " + str(len(problem)) + " problems.")
if idx == -1:
idx = int(input("Which problem do you want so solve? [0-" + str(len(problem)-1) + "] "))
problem = problem[idx]
print(problem)
sim.init(problem)
if not max_temp == -1:
if not max_steps == -1:
if not accept_prob == -1:
solution = sim.anneal(max_temp, max_steps, accept_prob)
else:
solution = sim.anneal(max_temp = max_temp, max_steps = max_steps)
else:
if not accept_prob == -1:
solution = sim.anneal(max_temp = max_temp, accept_prob = accept_prob)
else:
solution = sim.anneal(max_temp = max_temp)
else:
if not max_steps == -1:
if not accept_prob == -1:
solution = sim.anneal(max_steps = max_steps, accept_prob = accept_prob)
else:
solution = sim.anneal(max_steps = max_steps)
else:
if not accept_prob == -1:
solution = sim.anneal(accept_prob = accept_prob)
else:
solution = sim.anneal()
print(solution)
print(sim.rate(solution))
if plot:
o.create_plot(problem, solution)
if __name__ == "__main__":
if "__name__" == "__main__":
main()