From 626ffdd4e20e82d0361941c482ec222474f792a9 Mon Sep 17 00:00:00 2001 From: Trolli Schmittlauch Date: Tue, 20 Jun 2017 16:14:04 +0200 Subject: [PATCH 01/30] clarify index j --- notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notes.md b/notes.md index a21ba6f..efe7c46 100644 --- a/notes.md +++ b/notes.md @@ -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) +1. Liste von (T, $o_j$) mit $T \in \mathbb{N}$ (Time), $o_j \in O$ (Tasks), j bezeichnet den Job - Operationen: - Vertauschen von 2 Jobs auf einer Maschine, selbstinvers - Verzögern von Operationen (keine expliziten Wartezustände nötig) From 26c229929d6a86d724b72d39d6d15d6e8c3b8ac9 Mon Sep 17 00:00:00 2001 From: Trolli Schmittlauch Date: Tue, 20 Jun 2017 16:25:18 +0200 Subject: [PATCH 02/30] require python 3.6 --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index b2820bc..52607a2 100644 --- a/Readme.md +++ b/Readme.md @@ -2,7 +2,7 @@ ## Tooling -- Python 3.5 +- Python 3.6 - [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 ) From 21f78a0479a70ff7f9ea6cec913b6531a4e26de2 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Fri, 23 Jun 2017 19:00:50 +0200 Subject: [PATCH 03/30] initial documentation --- doc.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 doc.md diff --git a/doc.md b/doc.md new file mode 100644 index 0000000..4eed2da --- /dev/null +++ b/doc.md @@ -0,0 +1,37 @@ +## 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 job on a machine any given time + +## implementation in Python + - translate jobs into lists of tasks, problem into list of jobs, 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 job of the first task. + - compute only one possible next solution, rate, drop/accept. $\delta$ is computed iteratively during generation + - From 58c386175e427545f2c644b9c707f2b3125fc2d4 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Fri, 23 Jun 2017 19:05:31 +0200 Subject: [PATCH 04/30] merge --- doc.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc.md b/doc.md index 4eed2da..b9b2076 100644 --- a/doc.md +++ b/doc.md @@ -34,4 +34,3 @@ Both operations require that the start times are recomputed. - translate jobs into lists of tasks, problem into list of jobs, 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 job of the first task. - compute only one possible next solution, rate, drop/accept. $\delta$ is computed iteratively during generation - - From e82512f2b0a6a7e9bf1df775229324262a3582fc Mon Sep 17 00:00:00 2001 From: Trolli Schmittlauch Date: Mon, 26 Jun 2017 14:16:02 +0200 Subject: [PATCH 05/30] correct possible ambiguation of and --- doc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc.md b/doc.md index b9b2076..8d63bd9 100644 --- a/doc.md +++ b/doc.md @@ -31,6 +31,6 @@ Both operations require that the start times are recomputed. - only one job on a machine any given time ## implementation in Python - - translate jobs into lists of tasks, problem into list of jobs, 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 job of the first task. + - 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 From 457eaf74be3bd898c6420ada9813140fc92891b3 Mon Sep 17 00:00:00 2001 From: maxschlosser Date: Mon, 26 Jun 2017 21:43:14 +0200 Subject: [PATCH 06/30] Update 'doc.md' --- doc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc.md b/doc.md index 8d63bd9..fd1f7b9 100644 --- a/doc.md +++ b/doc.md @@ -28,7 +28,7 @@ Both operations require that the start times are recomputed. ## 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 job on a machine any given time + - 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$] From e3945ce3dce412eb4e8ae7a752504d55def67a96 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Thu, 29 Jun 2017 19:14:24 +0200 Subject: [PATCH 07/30] solve by enumeration --- src/SchedulingAlgorithms/enumerate.py | 10 ++++++++++ src/SchedulingAlgorithms/lpt.py | 6 ++++++ src/SchedulingAlgorithms/simanneal.py | 0 3 files changed, 16 insertions(+) create mode 100644 src/SchedulingAlgorithms/enumerate.py create mode 100644 src/SchedulingAlgorithms/lpt.py create mode 100644 src/SchedulingAlgorithms/simanneal.py diff --git a/src/SchedulingAlgorithms/enumerate.py b/src/SchedulingAlgorithms/enumerate.py new file mode 100644 index 0000000..7b50a9c --- /dev/null +++ b/src/SchedulingAlgorithms/enumerate.py @@ -0,0 +1,10 @@ +from JobShopParser import JobShopProblem as Problem + +def enumerate(problem): + schedule = [ (job, task) for job in range(0, problem.jobs) for task in range(0, len(problem.problem_data[job])) ] + begin = 0 + solution = [] + for task in schedule: + solution.append((begin, task)) + begin += problem.problem_data[task[0]][task[1]][0] + print(solution) diff --git a/src/SchedulingAlgorithms/lpt.py b/src/SchedulingAlgorithms/lpt.py new file mode 100644 index 0000000..70eeb01 --- /dev/null +++ b/src/SchedulingAlgorithms/lpt.py @@ -0,0 +1,6 @@ +from JobShopParser import * + +def lpt(problem : JobShopProblem): + while problem: + ready = [ job[0] for job in problem.problem_data ] + req_machines = [ task[1] for task in ready ] diff --git a/src/SchedulingAlgorithms/simanneal.py b/src/SchedulingAlgorithms/simanneal.py new file mode 100644 index 0000000..e69de29 From 23e6d12754d4f54e37dedf027663997431a62b19 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Sun, 2 Jul 2017 14:07:38 +0200 Subject: [PATCH 08/30] enumerate tasks --- inputdata/sample | 4 ++++ src/SchedulingAlgorithms/enumerate.py | 8 ++++---- src/SchedulingAlgorithms/lpt.py | 6 ------ 3 files changed, 8 insertions(+), 10 deletions(-) create mode 100644 inputdata/sample delete mode 100644 src/SchedulingAlgorithms/lpt.py diff --git a/inputdata/sample b/inputdata/sample new file mode 100644 index 0000000..0ace874 --- /dev/null +++ b/inputdata/sample @@ -0,0 +1,4 @@ +3 3 +0 4 1 6 2 1 +1 3 0 13 2 4 +1 2 2 5 0 3 diff --git a/src/SchedulingAlgorithms/enumerate.py b/src/SchedulingAlgorithms/enumerate.py index 7b50a9c..558ea12 100644 --- a/src/SchedulingAlgorithms/enumerate.py +++ b/src/SchedulingAlgorithms/enumerate.py @@ -1,10 +1,10 @@ -from JobShopParser import JobShopProblem as Problem +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.problem_data[job])) ] + schedule = ( (job, task) for job in range(0, problem.jobs) for task in range(0, len(problem[job])) ) begin = 0 solution = [] for task in schedule: solution.append((begin, task)) - begin += problem.problem_data[task[0]][task[1]][0] - print(solution) + begin += problem[task[0]][task[1]][0] + return solution diff --git a/src/SchedulingAlgorithms/lpt.py b/src/SchedulingAlgorithms/lpt.py deleted file mode 100644 index 70eeb01..0000000 --- a/src/SchedulingAlgorithms/lpt.py +++ /dev/null @@ -1,6 +0,0 @@ -from JobShopParser import * - -def lpt(problem : JobShopProblem): - while problem: - ready = [ job[0] for job in problem.problem_data ] - req_machines = [ task[1] for task in ready ] From a8c4a11b20b05b888ea7e0486e1a8ee2335554d7 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Sun, 2 Jul 2017 19:32:26 +0200 Subject: [PATCH 09/30] mock() to generate mockenv for the generator, pull_fwd() to compute new solutions. Rectify needs to be implemented before generator works. --- src/Generator/generator.py | 77 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/Generator/generator.py diff --git a/src/Generator/generator.py b/src/Generator/generator.py new file mode 100644 index 0000000..bb7ac34 --- /dev/null +++ b/src/Generator/generator.py @@ -0,0 +1,77 @@ +from Parser import JobShopProblem as Problem +import random + + +""" 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. +""" +def pull_fwd(solution): + old_idx = random.randint(1, len(solution)-1) + while(solution[old_idx][1][0] == solution[old_idx-1][1][0]): + old_idx -= 1 + new_idx = random.randint(0, old_idx-1) + for task in solution[new:old_idx]: + if(task[1][0] == solution[old_idx][1][0]): + break + else: + new_solution = solution[:] + task = solution[old_idx] + new_solution.remove(task) + new_solution.insert(new_idx, task) + return (new_solution, new_idx) + + +""" Accept the current generated solution and evaluate it. + Maybe skip this during the first step to generate a more + random solution. +""" +def accept(solution): + return 3 + + +""" Transform solution by adapting the begin times and delaying + tasks on the same machine if affected. +""" +def rectify(solution, idx): + global problem + + #move previous task on the same machine back if clash +""" task = solution[idx][1] + prev_task = solution[idx-1][1] + while(problem[task[0]][task[1]][1] == problem[prev_task[0]][prev_task[1]][1]): + solution[idx-1][0] += problem[task[0]][task[1]][0] + solution[idx], solution[idx-1] = solution[idx-1], solution[idx] + idx -= 1 + task = solution[idx][1] + prev_task = solution[idx-1][1] + del prev_task +""" + #make parallel + solution[idx][0] = solution[idx-1][0] + + +"""Generate a new solution from an existing solution with a + specified number of max steps. +""" +def generate(solution, steps): + options = [pull_fwd, accept] + option = random.choice(options) + return option(solution) + + +""" Reads a mock problem and creates the corresponding enumerated + solution. Should clean up the namespace afterwards. +""" +def mock(): + global problem + global solution + 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 From bd370a1ed5b7936d235154b3d9e90a670eedb5a2 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Mon, 3 Jul 2017 16:11:30 +0200 Subject: [PATCH 10/30] Put docstrings at the right(?) positions. --- src/Generator/generator.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Generator/generator.py b/src/Generator/generator.py index bb7ac34..057cf2f 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -2,6 +2,8 @@ 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 @@ -9,7 +11,6 @@ import random Returns the modified solution and the tasks index. """ -def pull_fwd(solution): old_idx = random.randint(1, len(solution)-1) while(solution[old_idx][1][0] == solution[old_idx-1][1][0]): old_idx -= 1 @@ -25,18 +26,18 @@ def pull_fwd(solution): return (new_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. """ -def accept(solution): return 3 +def rectify(solution, idx): """ Transform solution by adapting the begin times and delaying tasks on the same machine if affected. """ -def rectify(solution, idx): global problem #move previous task on the same machine back if clash @@ -54,19 +55,19 @@ def rectify(solution, idx): solution[idx][0] = solution[idx-1][0] +def generate(solution, steps): """Generate a new solution from an existing solution with a specified number of max steps. """ -def generate(solution, steps): options = [pull_fwd, accept] option = random.choice(options) return option(solution) +def mock(): """ Reads a mock problem and creates the corresponding enumerated solution. Should clean up the namespace afterwards. """ -def mock(): global problem global solution from Parser.js2_style import parse_file as mockload From 85e61f91fb5283768aad16a26f2dadbf93e229bf Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Sun, 9 Jul 2017 18:57:52 +0200 Subject: [PATCH 11/30] Multiple methods for rectify, merge of mapping. TODO: rectify loop --- src/Generator/generator.py | 110 +++++++++++++++++++------- src/SchedulingAlgorithms/enumerate.py | 2 +- 2 files changed, 82 insertions(+), 30 deletions(-) diff --git a/src/Generator/generator.py b/src/Generator/generator.py index 057cf2f..907f00d 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -4,70 +4,122 @@ import random def pull_fwd(solution): -""" Pull a task from a pseudo-random position to the position of + """ + 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) while(solution[old_idx][1][0] == solution[old_idx-1][1][0]): old_idx -= 1 new_idx = random.randint(0, old_idx-1) - for task in solution[new:old_idx]: + for task in solution[new_idx:old_idx]: if(task[1][0] == solution[old_idx][1][0]): break else: - new_solution = solution[:] task = solution[old_idx] - new_solution.remove(task) - new_solution.insert(new_idx, task) - return (new_solution, new_idx) + solution.remove(task) + solution.insert(new_idx, task) + return (solution, new_idx) def accept(solution): -""" Accept the current generated solution and evaluate it. + """ + Accept the current generated solution and evaluate it. Maybe skip this during the first step to generate a more random solution. -""" + """ return 3 - +#TODO: Loop over successors def rectify(solution, idx): -""" Transform solution by adapting the begin times and delaying + """ + Transform solution by adapting the begin times and delaying tasks on the same machine if affected. -""" + """ + solution[idx][0] = solution[idx+1][0] + + update_begin(solution, idx) + correct_indices(solution, idx) + for task in solution[idx:]: + correct_machine(solution, solution.index(task)) + correct_precedence(solution, solution.index(task)) + +def update_begin(solution, idx): + """ + Update the start time of the given task. + """ + global problem + task = solution[idx] - #move previous task on the same machine back if clash -""" task = solution[idx][1] - prev_task = solution[idx-1][1] - while(problem[task[0]][task[1]][1] == problem[prev_task[0]][prev_task[1]][1]): - solution[idx-1][0] += problem[task[0]][task[1]][0] - solution[idx], solution[idx-1] = solution[idx-1], solution[idx] - idx -= 1 - task = solution[idx][1] - prev_task = solution[idx-1][1] - del prev_task -""" - #make parallel - solution[idx][0] = solution[idx-1][0] + machine = ( x for x in solution[idx-1::-1] if problem[x[1]][1] == problem[task[1]][1] ) + prev_mach = next(machine, 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][0] = max(end_mach, end_job, task[0]) +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) + +def correct_machine(solution, idx): + """ + Push jobs on machines back if conflicts exist. + """ + 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][0] = end + + +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 ), None) + if(conflict): + idx = solution.index(conflict) + solution[idx][0] = end + def generate(solution, steps): -"""Generate a new solution from an existing solution with a + """ + Generate a new solution from an existing solution with a specified number of max steps. -""" + """ + solution = solution[:] options = [pull_fwd, accept] option = random.choice(options) return option(solution) def mock(): -""" Reads a mock problem and creates the corresponding enumerated + """ + Reads a mock problem and creates the corresponding enumerated solution. Should clean up the namespace afterwards. -""" + """ global problem global solution from Parser.js2_style import parse_file as mockload diff --git a/src/SchedulingAlgorithms/enumerate.py b/src/SchedulingAlgorithms/enumerate.py index 558ea12..64f9086 100644 --- a/src/SchedulingAlgorithms/enumerate.py +++ b/src/SchedulingAlgorithms/enumerate.py @@ -1,7 +1,7 @@ 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[job])) ) + schedule = ( (job, task) for job in range(0, problem.jobs) for task in range(0, problem.get_tasks_by_job(job)) ) begin = 0 solution = [] for task in schedule: From a7f1a8674899659026b3f11460c603994a431ffe Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Mon, 10 Jul 2017 15:59:00 +0200 Subject: [PATCH 12/30] Some small documentation, hopefully useful --- src/Generator/generator.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Generator/generator.py b/src/Generator/generator.py index 907f00d..f9f2c93 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -34,7 +34,8 @@ def accept(solution): """ return 3 -#TODO: Loop over successors +#TODO: Loop over successors. The correct_methods might need to be +#changed so the changes are correctly passed on def rectify(solution, idx): """ Transform solution by adapting the begin times and delaying @@ -50,14 +51,16 @@ def rectify(solution, idx): def update_begin(solution, idx): """ - Update the start time of the given task. + Update the start time of the given task wrt machine and job. """ global problem task = solution[idx] + #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) + 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 @@ -66,7 +69,7 @@ def update_begin(solution, idx): 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][0] = max(end_mach, end_job, task[0]) + solution[idx][0] = max(end_mach, end_job, task[0]) def correct_indices(solution, idx): @@ -78,10 +81,11 @@ def correct_indices(solution, idx): if tasks: solution.remove(task) solution.insert(idx + len(tasks), task) + #[1,3,2] -> idx = 1, len([2])=1 def correct_machine(solution, idx): """ - Push jobs on machines back if conflicts exist. + Check conflicts on machines and correct if needed. """ task = solution[idx] end = problem[task[1]][0] + task[0] @@ -104,12 +108,12 @@ def correct_precedence(solution, idx): idx = solution.index(conflict) solution[idx][0] = end -def generate(solution, steps): +def generate(old_solution, steps): """ Generate a new solution from an existing solution with a specified number of max steps. """ - solution = solution[:] + solution = old_solution[:] options = [pull_fwd, accept] option = random.choice(options) return option(solution) From 6be2e20cc7e43c3100c52e2bd3fe7cb45af70f92 Mon Sep 17 00:00:00 2001 From: lukasstracke Date: Mon, 10 Jul 2017 23:17:33 +0200 Subject: [PATCH 13/30] =?UTF-8?q?Pull=5FFwd=20inkl.=20Rectify=20funktionst?= =?UTF-8?q?=C3=BCchtig=20TODO:=20Accept=20+=20Simanneal=20+=20Output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Generator/generator.py | 65 +++++++++++++++++++-------- src/SchedulingAlgorithms/enumerate.py | 4 +- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/Generator/generator.py b/src/Generator/generator.py index f9f2c93..7e4dbd7 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -13,17 +13,23 @@ def pull_fwd(solution): 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 - else: - task = solution[old_idx] - solution.remove(task) - solution.insert(new_idx, task) - return (solution, new_idx) + #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): @@ -41,13 +47,16 @@ def rectify(solution, idx): Transform solution by adapting the begin times and delaying tasks on the same machine if affected. """ - solution[idx][0] = solution[idx+1][0] + solution[idx] = (solution[idx+1][0],) + solution[idx][1:] update_begin(solution, idx) correct_indices(solution, idx) - for task in solution[idx:]: - correct_machine(solution, solution.index(task)) - correct_precedence(solution, solution.index(task)) + for i in range(idx, len(solution)-1): + print("i: " + str(i)) + correct_machine(solution, i) + print(solution) + correct_precedence(solution, i) + print(solution) def update_begin(solution, idx): """ @@ -57,19 +66,23 @@ def update_begin(solution, idx): 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) + 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][0] = max(end_mach, end_job, task[0]) + solution[idx] = (max(end_mach, end_job, task[0]),) + solution[idx][1:] def correct_indices(solution, idx): @@ -93,7 +106,8 @@ def correct_machine(solution, idx): conflict = next(( x for x in possible_conf if x[0] < end ), None) if(conflict): idx = solution.index(conflict) - solution[idx][0] = end + solution[idx] = (end,) + solution[idx][1:] + correct_indices(solution,idx) def correct_precedence(solution, idx): @@ -101,12 +115,21 @@ def correct_precedence(solution, idx): Check precedence relation and correct if needed. """ task = solution[idx] - end = problem[task[1][0] + task[0] + 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 ), None) + 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) - solution[idx][0] = end + 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): """ @@ -115,9 +138,13 @@ def generate(old_solution, steps): """ solution = old_solution[:] options = [pull_fwd, accept] - option = random.choice(options) - return option(solution) - + while(steps > 0): + option = random.choice(options) + solution = option(solution) + if(option == accept): + break + steps -= 1 + return solution def mock(): """ diff --git a/src/SchedulingAlgorithms/enumerate.py b/src/SchedulingAlgorithms/enumerate.py index 64f9086..fb291ba 100644 --- a/src/SchedulingAlgorithms/enumerate.py +++ b/src/SchedulingAlgorithms/enumerate.py @@ -1,10 +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, problem.get_tasks_by_job(job)) ) + 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]][task[1]][0] + begin += problem[task][0] return solution From 6783d91c43d508abb4d35b15e4da33587610b570 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Tue, 11 Jul 2017 12:07:03 +0200 Subject: [PATCH 14/30] tighten --- src/Generator/generator.py | 48 ++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/Generator/generator.py b/src/Generator/generator.py index 7e4dbd7..ed1a48e 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -13,11 +13,11 @@ def pull_fwd(solution): Returns the modified solution and the tasks index. """ old_idx = random.randint(1, len(solution)-1) - print("old_idx" + str(old_idx)) + #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)) + #print("old_idx: " + str(old_idx)) if(old_idx == 0): return pull_fwd(solution) new_idx = random.randint(0, old_idx-1) @@ -38,10 +38,32 @@ def accept(solution): Maybe skip this during the first step to generate a more random solution. """ - return 3 + 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 -#TODO: Loop over successors. The correct_methods might need to be -#changed so the changes are correctly passed on def rectify(solution, idx): """ Transform solution by adapting the begin times and delaying @@ -52,11 +74,11 @@ def rectify(solution, idx): update_begin(solution, idx) correct_indices(solution, idx) for i in range(idx, len(solution)-1): - print("i: " + str(i)) + #print("i: " + str(i)) correct_machine(solution, i) - print(solution) + #print(solution) correct_precedence(solution, i) - print(solution) + #print(solution) def update_begin(solution, idx): """ @@ -120,13 +142,13 @@ def correct_precedence(solution, idx): 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)) + #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)) + #print("new_start: " + str(new_start)) solution.remove(task) task = (new_start,) + task[1:] solution.insert(idx, task) @@ -159,3 +181,9 @@ def mock(): solution = mockenum(problem) del mockload del mockenum + +def init(in_problem, in_solution): + global problem + global solution + problem = in_problem + solution = in_solution From 304dc401f741a5c29cec7692beed9b5a2031082f Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Tue, 11 Jul 2017 13:18:14 +0200 Subject: [PATCH 15/30] Added matplotlib as requirement --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 4612f56..5556179 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ mypy Arpeggio +matplotlib From 32c99ff0759db78f11c03b52eecef9e63e417964 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Tue, 11 Jul 2017 13:50:51 +0200 Subject: [PATCH 16/30] accept, tighten, generate rewrite (now has magic number describing percentage) --- src/Generator/generator.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Generator/generator.py b/src/Generator/generator.py index ed1a48e..1d115b8 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -38,7 +38,7 @@ def accept(solution): Maybe skip this during the first step to generate a more random solution. """ - tighten(solution) + return tighten(solution) def tighten(solution): @@ -79,6 +79,7 @@ def rectify(solution, idx): #print(solution) correct_precedence(solution, i) #print(solution) + return solution def update_begin(solution, idx): """ @@ -159,13 +160,16 @@ def generate(old_solution, steps): specified number of max steps. """ solution = old_solution[:] - options = [pull_fwd, accept] + accept_percent = 1 + option = pull_fwd #do at least one pull while(steps > 0): - option = random.choice(options) solution = option(solution) if(option == accept): break steps -= 1 + select = random.randrange(0,100) + option = pull_fwd if (select - accept_percent) > 0 else accept + accept(solution) return solution def mock(): @@ -174,16 +178,14 @@ def mock(): solution. Should clean up the namespace afterwards. """ global problem - global solution 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, in_solution): +def init(in_problem): global problem - global solution problem = in_problem - solution = in_solution From 049e64c67524fc99031ea1bc74e66a0f4d942679 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Tue, 11 Jul 2017 13:55:02 +0200 Subject: [PATCH 17/30] Magic number is now a default parameter --- src/Generator/generator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Generator/generator.py b/src/Generator/generator.py index 1d115b8..9642bdd 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -154,13 +154,13 @@ def correct_precedence(solution, idx): task = (new_start,) + task[1:] solution.insert(idx, task) -def generate(old_solution, steps): +def generate(old_solution, steps, percent=1): """ Generate a new solution from an existing solution with a specified number of max steps. """ solution = old_solution[:] - accept_percent = 1 + percent = 1 option = pull_fwd #do at least one pull while(steps > 0): solution = option(solution) @@ -168,7 +168,7 @@ def generate(old_solution, steps): break steps -= 1 select = random.randrange(0,100) - option = pull_fwd if (select - accept_percent) > 0 else accept + option = pull_fwd if (select - percent) > 0 else accept accept(solution) return solution From 991b7f4175f0e0908d1c04cbbe0491b7a44d46d1 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Tue, 11 Jul 2017 22:25:56 +0200 Subject: [PATCH 18/30] simulated annealing --- src/Generator/generator.py | 15 +++++++---- src/SchedulingAlgorithms/simanneal.py | 36 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/Generator/generator.py b/src/Generator/generator.py index 9642bdd..6920d4b 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -159,16 +159,21 @@ def generate(old_solution, steps, percent=1): Generate a new solution from an existing solution with a specified number of max steps. """ + import sys + print("Max steps: " + str(steps)) + sys.stdout.write("Start generation... ") solution = old_solution[:] - percent = 1 option = pull_fwd #do at least one pull - while(steps > 0): + for i in range(0, steps): solution = option(solution) if(option == accept): break - steps -= 1 - select = random.randrange(0,100) - option = pull_fwd if (select - percent) > 0 else accept + select = random.randrange(0,1000) + option = pull_fwd #if (select - percent) > 0 else accept + if ((i * 100) % steps == 0): + sys.stdout.write(str(i*100/steps) + "%... ") + sys.stdout.flush() + sys.stdout.write("\n") accept(solution) return solution diff --git a/src/SchedulingAlgorithms/simanneal.py b/src/SchedulingAlgorithms/simanneal.py index e69de29..082b64f 100644 --- a/src/SchedulingAlgorithms/simanneal.py +++ b/src/SchedulingAlgorithms/simanneal.py @@ -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 = 1000, max_steps = 1000): + global problem + gen_init(problem) + temp = max_temp + initial = enum(problem) + current = generate(initial, problem.machines * problem.jobs * 10) + del initial + for step in range(0, max_steps): + new = generate(current, problem.machines * problem.jobs) + 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 From 3a7a8ebcbd8766c7b0f9b407ef30b863f58a7f91 Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Wed, 12 Jul 2017 04:23:36 +0200 Subject: [PATCH 19/30] Somewhat sensible default parameters, temperature, amount of maximum anneal iterations and generator accept probability passed to anneal. --- src/Generator/generator.py | 11 ++++++----- src/SchedulingAlgorithms/simanneal.py | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Generator/generator.py b/src/Generator/generator.py index 6920d4b..13d3441 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -154,13 +154,14 @@ def correct_precedence(solution, idx): task = (new_start,) + task[1:] solution.insert(idx, task) -def generate(old_solution, steps, percent=1): +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 @@ -168,13 +169,13 @@ def generate(old_solution, steps, percent=1): solution = option(solution) if(option == accept): break - select = random.randrange(0,1000) - option = pull_fwd #if (select - percent) > 0 else accept + 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("\n") - accept(solution) + sys.stdout.write("Done\n") + if option != accept: + accept(solution) return solution def mock(): diff --git a/src/SchedulingAlgorithms/simanneal.py b/src/SchedulingAlgorithms/simanneal.py index 082b64f..66d8818 100644 --- a/src/SchedulingAlgorithms/simanneal.py +++ b/src/SchedulingAlgorithms/simanneal.py @@ -4,15 +4,15 @@ from SchedulingAlgorithms.enumerate import enumerate as enum from math import e from random import random -def anneal(max_temp = 1000, max_steps = 1000): +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) + 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) + 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))) From be28f9b11fdbffef6c4f94741840c6fcc7513655 Mon Sep 17 00:00:00 2001 From: lukasstracke Date: Wed, 12 Jul 2017 16:33:53 +0200 Subject: [PATCH 20/30] Output 1.0, Beispiel s. example.py --- src/Output/output.py | 22 ++++++++++++++++++++++ src/example.py | 14 ++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 src/Output/output.py diff --git a/src/Output/output.py b/src/Output/output.py new file mode 100644 index 0000000..615f6d1 --- /dev/null +++ b/src/Output/output.py @@ -0,0 +1,22 @@ +import matplotlib.pyplot as plt +from matplotlib import colors +import numpy as np +from SchedulingAlgorithms.simanneal import rate + +def create_plot(problem, solution): + + end = rate(solution) + + with plt.xkcd(): + fig,ax = plt.subplots() + 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=[list(colors.XKCD_COLORS.values())[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)] ) + + plt.show() \ No newline at end of file diff --git a/src/example.py b/src/example.py index a56e489..be8b4dd 100644 --- a/src/example.py +++ b/src/example.py @@ -1,7 +1,9 @@ -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)]] +from Generator import generator as g +from SchedulingAlgorithms import simanneal as sim +from Output import output as o +g.mock() +problem = g.problem +sim.init(problem) +solution = sim.anneal() +o.create_plot(problem, solution) \ No newline at end of file From d88e5d9e9155f48749a395c784900a6072169ae7 Mon Sep 17 00:00:00 2001 From: lukasstracke Date: Wed, 12 Jul 2017 17:50:36 +0200 Subject: [PATCH 21/30] Legendary Output, v1.1 --- src/Generator/generator.py | 3 ++- src/Output/output.py | 10 +++++++++- src/example.py | 7 ++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Generator/generator.py b/src/Generator/generator.py index 13d3441..d6dca45 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -38,7 +38,8 @@ def accept(solution): Maybe skip this during the first step to generate a more random solution. """ - return tighten(solution) + #return tighten(solution) + return solution def tighten(solution): diff --git a/src/Output/output.py b/src/Output/output.py index 615f6d1..6f06aed 100644 --- a/src/Output/output.py +++ b/src/Output/output.py @@ -1,7 +1,9 @@ import matplotlib.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): @@ -9,14 +11,20 @@ def create_plot(problem, solution): with plt.xkcd(): fig,ax = plt.subplots() + colorlist = list(colors.XKCD_COLORS.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=[list(colors.XKCD_COLORS.values())[x[1][0]] for x in mach_ops]) + 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() \ No newline at end of file diff --git a/src/example.py b/src/example.py index be8b4dd..14ed58a 100644 --- a/src/example.py +++ b/src/example.py @@ -1,9 +1,10 @@ -from Generator import generator as g +#import Parser.js1_style as p +import Parser.js2_style as p from SchedulingAlgorithms import simanneal as sim from Output import output as o -g.mock() -problem = g.problem +#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) \ No newline at end of file From 460a4a816681588a8a937263cbe92cc562b6084d Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Wed, 12 Jul 2017 17:55:16 +0200 Subject: [PATCH 22/30] main method, command line options --- src/main.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/src/main.py b/src/main.py index e1e7dda..68fe28e 100644 --- a/src/main.py +++ b/src/main.py @@ -1,6 +1,65 @@ #! /usr/bin/env python -def main() -> None: - pass -if "__name__" == "__main__": +import sys +import getopt +from SchedulingAlgorithms import simanneal as sim +from Outpout import output as o + + +def usage(): + s= """ +Command line options: + -h show this help + -p activate pretty output (matplotlib) + -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 + +Invocation: + python [-hlp] file +""" + return s + + +def main(): + js1 = False + plot = False + try: + opts, args = getopt.getopt(sys.argv[1:], 'hpli:') + except getoptGetoptError as err: + print(err) + sys.exit() + if ('-h', '') in opts: + print(usage()) + if ('-p', '') in opts: + print("Plotting enabled.") + 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 + 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 and idx == -1: + print("File contains " + str(len(problem)) + " problems.") + idx = int(input("Which problem do you want so solve? [0-" + str(len(problem)-1) + "] ")) + problem = problem[idx] + print(problem) + sim.init(problem) + solution = sim.anneal() + print(solution) + print(sim.rate(solution)) + if plot: + o.create_plot(problem, solution) + +if __name__ == "__main__": main() + From 2a214b93bd2ca769a0df9c7e5a33d49ce13815e1 Mon Sep 17 00:00:00 2001 From: Trolli Schmittlauch Date: Wed, 12 Jul 2017 18:00:22 +0200 Subject: [PATCH 23/30] fix import typo contributes to #20 --- src/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 68fe28e..fa3323f 100644 --- a/src/main.py +++ b/src/main.py @@ -3,7 +3,7 @@ import sys import getopt from SchedulingAlgorithms import simanneal as sim -from Outpout import output as o +from Output import output as o def usage(): From 3ccbed4d3a876ff93a7d8e7c8ff326ed328c52cf Mon Sep 17 00:00:00 2001 From: Maximilian Schlosser Date: Wed, 12 Jul 2017 18:17:05 +0200 Subject: [PATCH 24/30] pretty printing --- src/Output/output.py | 4 ++-- src/main.py | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Output/output.py b/src/Output/output.py index 6f06aed..2eb31ec 100644 --- a/src/Output/output.py +++ b/src/Output/output.py @@ -1,4 +1,4 @@ -import matplotlib.pyplot as plt +from matplotlib import pyplot as plt from matplotlib import colors from matplotlib import patches import numpy as np @@ -27,4 +27,4 @@ def create_plot(problem, solution): labels = ["Job "+str(j) for j in range(0,problem.jobs)] ax.legend(handles, labels) - plt.show() \ No newline at end of file + plt.show() diff --git a/src/main.py b/src/main.py index 68fe28e..ff46783 100644 --- a/src/main.py +++ b/src/main.py @@ -3,14 +3,13 @@ import sys import getopt from SchedulingAlgorithms import simanneal as sim -from Outpout import output as o def usage(): s= """ Command line options: -h show this help - -p activate pretty output (matplotlib) + -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 @@ -32,6 +31,7 @@ def main(): print(usage()) if ('-p', '') in opts: print("Plotting enabled.") + from Output import output as o plot = True if('-l', '') in opts: js1 = True @@ -48,10 +48,11 @@ def main(): from Parser import js2_style as parser print("Parsing file: " + infile) problem = parser.parse_file(infile) - if js1 and idx == -1: + if js1: print("File contains " + str(len(problem)) + " problems.") - idx = int(input("Which problem do you want so solve? [0-" + str(len(problem)-1) + "] ")) - problem = problem[idx] + 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) solution = sim.anneal() From 52a2f0ca2f3af66db581c04b25317f46394eeddb Mon Sep 17 00:00:00 2001 From: Trolli Schmittlauch Date: Wed, 12 Jul 2017 20:06:18 +0200 Subject: [PATCH 25/30] fix remaining merge conflict artifacts --- src/main.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main.py b/src/main.py index 02066ae..31c5c07 100644 --- a/src/main.py +++ b/src/main.py @@ -3,10 +3,7 @@ import sys import getopt from SchedulingAlgorithms import simanneal as sim -<<<<<<< HEAD -======= from Output import output as o ->>>>>>> origin/devel def usage(): From 9574aca0b9f197f45d5edb14d4ff5448d86247ae Mon Sep 17 00:00:00 2001 From: Trolli Schmittlauch Date: Wed, 12 Jul 2017 21:58:53 +0200 Subject: [PATCH 26/30] follow python package convention --- src/Generator/__init__.py | 0 src/Output/__init__.py | 0 src/SchedulingAlgorithms/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/Generator/__init__.py create mode 100644 src/Output/__init__.py create mode 100644 src/SchedulingAlgorithms/__init__.py diff --git a/src/Generator/__init__.py b/src/Generator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/Output/__init__.py b/src/Output/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SchedulingAlgorithms/__init__.py b/src/SchedulingAlgorithms/__init__.py new file mode 100644 index 0000000..e69de29 From 03c5fb612dad7a1af498d6a66c55ef59eee28e0f Mon Sep 17 00:00:00 2001 From: lukasstracke Date: Thu, 13 Jul 2017 00:03:27 +0200 Subject: [PATCH 27/30] =?UTF-8?q?finale=20Version:=20Readme=20f=C3=BCr=20D?= =?UTF-8?q?r.=20Gaggl,=20Reqs=20erg=C3=A4nzt=20(kp=20ob=20notwendig),=20we?= =?UTF-8?q?iss=20als=20Farbe=20im=20Plot=20ausschliessen,=20main=20um=20si?= =?UTF-8?q?manneal-params=20ergaenzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Readme.txt | 23 +++++++++++++++++++++++ requirements.txt | 4 +++- src/Generator/generator.py | 4 ++-- src/Output/output.py | 4 +++- src/example.py | 8 ++++---- src/main.py | 36 +++++++++++++++++++++++++++++++++--- 6 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 Readme.txt diff --git a/Readme.txt b/Readme.txt new file mode 100644 index 0000000..2990537 --- /dev/null +++ b/Readme.txt @@ -0,0 +1,23 @@ +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 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5556179..55650c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ mypy -Arpeggio +arpeggio matplotlib +numpy +tkinter \ No newline at end of file diff --git a/src/Generator/generator.py b/src/Generator/generator.py index d6dca45..4cc4ab3 100644 --- a/src/Generator/generator.py +++ b/src/Generator/generator.py @@ -38,8 +38,8 @@ def accept(solution): Maybe skip this during the first step to generate a more random solution. """ - #return tighten(solution) - return solution + return tighten(solution) + #return solution def tighten(solution): diff --git a/src/Output/output.py b/src/Output/output.py index 2eb31ec..a2d2168 100644 --- a/src/Output/output.py +++ b/src/Output/output.py @@ -11,7 +11,9 @@ def create_plot(problem, solution): with plt.xkcd(): fig,ax = plt.subplots() - colorlist = list(colors.XKCD_COLORS.values()) + 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 ] diff --git a/src/example.py b/src/example.py index 14ed58a..50d2f22 100644 --- a/src/example.py +++ b/src/example.py @@ -1,10 +1,10 @@ -#import Parser.js1_style as p -import Parser.js2_style as p +import Parser.js1_style as p +#import Parser.js2_style as p from SchedulingAlgorithms import simanneal as sim from Output import output as o -#problem = p.parse_file("../inputdata/jobshop1.txt")[0] -problem = p.parse_file("../inputdata/sample") +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) \ No newline at end of file diff --git a/src/main.py b/src/main.py index 31c5c07..44f5448 100644 --- a/src/main.py +++ b/src/main.py @@ -13,6 +13,9 @@ Command line options: -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 @@ -24,8 +27,8 @@ def main(): js1 = False plot = False try: - opts, args = getopt.getopt(sys.argv[1:], 'hpli:') - except getoptGetoptError as err: + opts, args = getopt.getopt(sys.argv[1:], 'hpli:t:s:a:') + except getopt.GetoptError as err: print(err) sys.exit() if ('-h', '') in opts: @@ -38,6 +41,12 @@ def main(): 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() @@ -56,7 +65,28 @@ def main(): problem = problem[idx] print(problem) sim.init(problem) - solution = sim.anneal() + 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: From 412b572afb8dc3e1cbbc8576122b03d399d537da Mon Sep 17 00:00:00 2001 From: Trolli Schmittlauch Date: Sat, 19 Mar 2022 03:05:21 +0100 Subject: [PATCH 28/30] update grammar comments syntax to arpeggio-1.9.0 --- src/Parser/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Parser/__init__.py b/src/Parser/__init__.py index d42e09c..59d2edf 100644 --- a/src/Parser/__init__.py +++ b/src/Parser/__init__.py @@ -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 """ From c18dde9cc48edd6878b46923099bf7c4cb916992 Mon Sep 17 00:00:00 2001 From: Trolli Schmittlauch Date: Sat, 19 Mar 2022 03:04:34 +0100 Subject: [PATCH 29/30] add nix environment --- default.nix | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 default.nix diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..5963f18 --- /dev/null +++ b/default.nix @@ -0,0 +1,3 @@ +with import {}; + + (python3.withPackages (ps: [ps.numpy ps.matplotlib ps.mypy ps.tkinter ps.arpeggio])).env From f46b2cdd9f27d1a2e3c0a19584006a0213d7e852 Mon Sep 17 00:00:00 2001 From: Trolli Schmittlauch Date: Sat, 19 Mar 2022 03:04:50 +0100 Subject: [PATCH 30/30] move nix environment to fit lorri tooling --- default.nix | 3 --- shell.nix | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 default.nix create mode 100644 shell.nix diff --git a/default.nix b/default.nix deleted file mode 100644 index 5963f18..0000000 --- a/default.nix +++ /dev/null @@ -1,3 +0,0 @@ -with import {}; - - (python3.withPackages (ps: [ps.numpy ps.matplotlib ps.mypy ps.tkinter ps.arpeggio])).env diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..e8d926d --- /dev/null +++ b/shell.nix @@ -0,0 +1,3 @@ +with import {}; + + (python3.withPackages (ps: [ps.numpy (ps.matplotlib.override {enableQt=true;}) ps.mypy ps.arpeggio])).env