Skip to content

Commit dad9e7e

Browse files
committed
Many improvements ! New example "yeastfab", introduced gap_constraints, etc.
1 parent 07e1b76 commit dad9e7e

11 files changed

+446
-74
lines changed

Diff for: egfscheduler/__init__.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
# __all__ = []
44

5-
from .EGFScheduler import *
5+
from .classes import Resource, Task, WorkUnit
6+
from .numberjack_scheduler import numberjack_scheduler
7+
from .pyschedule_scheduler import pyschedule_scheduler
8+
from .plots import plot_schedule
9+
from .tools import gap_constraints
610

711
from .version import __version__

Diff for: egfscheduler/classes.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
import itertools as itt
2-
import numpy as np
3-
4-
import Numberjack as nj
52

63
tasks_counter = itt.count()
74
work_units_counter = itt.count()
@@ -40,12 +37,14 @@ class Task:
4037
Duration of the task (the choice of the unit is left to the user.
4138
4239
"""
43-
def __init__(self, name, resources, duration):
40+
41+
def __init__(self, name, resources, duration, scheduled=None):
4442

4543
self.resources = resources
4644
self.duration = duration
4745
self.name = name
4846
self.id = tasks_counter.next()
47+
self.scheduled = scheduled
4948

5049
def __repr__(self):
5150
return "Task(%(id)d, %(name)s, %(duration)s)" % self.__dict__
@@ -67,4 +66,4 @@ def __init__(self, name, capacity=1):
6766
self.capacity = capacity
6867

6968
def __repr__(self):
70-
return self.name
69+
return self.name

Diff for: egfscheduler/numberjack_scheduler.py

+24-66
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import itertools as itt
2-
import numpy as np
32
import Numberjack as nj
43

54

@@ -50,14 +49,27 @@ def numberjack_scheduler(work_units, upper_bound=500,
5049

5150
# Create Numberjack variables to represent the tasks
5251
# ( starting times and resource instance that they use).
53-
nj_tasks = {
54-
task: nj.Task(upper_bound, task.duration)
55-
for task in all_tasks
56-
}
52+
nj_tasks = {}
53+
for task in all_tasks:
54+
if task.scheduled is None:
55+
new_nj_task = nj.Task(upper_bound, task.duration)
56+
else:
57+
new_nj_task = nj.Task(
58+
task.scheduled[0],
59+
task.scheduled[0]+task.duration,
60+
task.duration
61+
)
62+
new_nj_task.name = task.name
63+
nj_tasks[task] = new_nj_task
5764

5865
nj_taskresources = {
59-
task: {resource: nj.Variable(1, resource.capacity)
60-
for resource in task.resources}
66+
task: {
67+
resource: (
68+
nj.Variable(1, resource.capacity)
69+
if task.scheduled is None else
70+
nj.Variable([task.scheduled[2][resource]])
71+
)
72+
for resource in task.resources}
6173
for task in all_tasks
6274
}
6375

@@ -70,7 +82,10 @@ def numberjack_scheduler(work_units, upper_bound=500,
7082
model = nj.Model()
7183

7284
for resource in all_resources:
73-
if resource.capacity == 1:
85+
86+
if resource.capacity == 'inf':
87+
continue
88+
elif resource.capacity == 1:
7489
# The resource has one slot: Only one job at the same time
7590
model.add(nj.UnaryResource([
7691
nj_tasks[task] for task in all_tasks
@@ -140,7 +155,7 @@ def numberjack_scheduler(work_units, upper_bound=500,
140155
# Plus a penalty just to compress a little more:
141156
# sum ( work_unit.t_end for all work_units)
142157
if optimize:
143-
C_max = nj.Variable(lower_bound, 1500000, 'C_max')
158+
C_max = nj.Variable(lower_bound, 15000000, 'C_max')
144159
model.add(
145160
C_max >
146161
sum([
@@ -186,60 +201,3 @@ def numberjack_scheduler(work_units, upper_bound=500,
186201
task.scheduled = (start, end, resources)
187202

188203
return work_units
189-
190-
191-
def plot_schedule(work_units):
192-
""" Plot the work units schedule in a gant-like way.
193-
194-
This is quite basic and arbitrary and really meant for R&D purposes.
195-
196-
Example
197-
-------
198-
199-
>>> # ... Make some work units
200-
>>> solve_constraints(work_units)
201-
>>> plot_schedule(work_units)
202-
203-
"""
204-
205-
try:
206-
import matplotlib.pyplot as plt
207-
import matplotlib.cm as cm
208-
except:
209-
raise ImportError("Plotting requires Matplotlib.")
210-
211-
all_resources = list(set([
212-
resource
213-
for work_unit in work_units
214-
for task in work_unit.tasks
215-
for resource in task.resources
216-
]))
217-
218-
colors = itt.cycle([cm.Paired(0.21*i % 1.0) for i in range(30)])
219-
fig, ax = plt.subplots(1, figsize=(15, 6))
220-
max_end = 0
221-
for work_unit in work_units:
222-
color = colors.next()
223-
label = work_unit.name
224-
for task in work_unit.tasks:
225-
start, end, resources = task.scheduled
226-
max_end = max(end, max_end)
227-
for y in [all_resources.index(resource) +
228-
0.2 + .6*resources[resource]/resource.capacity
229-
for resource in task.resources]:
230-
ax.plot([start, end], [y, y], color=color, lw=10, label=label)
231-
label = None
232-
233-
strips_colors = itt.cycle([(1, 1, 1), (1, 0.92, 0.92)])
234-
for i, color in zip(range(-1, len(all_resources)+1), strips_colors):
235-
ax.fill_between([0, 1.1*max_end], [i, i], y2=[i+1, i+1], color=color)
236-
237-
N = len(all_resources)
238-
ax.set_yticks(np.arange(N)+0.5)
239-
ax.set_ylim(-max(1, int(0.4*N)), max(2, int(1.5*N)))
240-
ax.set_yticklabels(all_resources)
241-
ax.legend(ncol=3, fontsize=8)
242-
ax.set_xlabel("time (a.u.)")
243-
ax.set_xlim(0, 1.1*max_end)
244-
245-
return fig, ax

Diff for: egfscheduler/plots.py

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import itertools as itt
2+
import numpy as np
3+
4+
MATPLOTLIB_AVAILABLE = True
5+
try:
6+
import matplotlib.pyplot as plt
7+
import matplotlib.patches as patches
8+
import matplotlib.cm as cm
9+
except:
10+
MATPLOTLIB_AVAILABLE = False
11+
12+
13+
def plot_schedule(work_units):
14+
""" Plot the work units schedule in a gant-like way.
15+
16+
This is quite basic and arbitrary and really meant for R&D purposes.
17+
18+
Example
19+
-------
20+
21+
>>> # ... Make some work units
22+
>>> solve_constraints(work_units)
23+
>>> plot_schedule(work_units)
24+
25+
"""
26+
27+
if not MATPLOTLIB_AVAILABLE:
28+
raise ImportError("Plotting requires Matplotlib.")
29+
30+
all_resources = sorted(list(set([
31+
resource
32+
for work_unit in work_units
33+
for task in work_unit.tasks
34+
for resource in task.resources
35+
])), key=lambda e: e.name)[::-1]
36+
37+
print all_resources
38+
39+
colors = itt.cycle([cm.Paired(0.21 * i % 1.0) for i in range(30)])
40+
fig, ax = plt.subplots(1, figsize=(15, 6))
41+
max_end = 0
42+
for work_unit in work_units:
43+
color = colors.next()
44+
ax.plot([0, 0], [0, 0], color=color, lw=8, label=work_unit.name)
45+
for task in work_unit.tasks:
46+
start, end, resources = task.scheduled
47+
max_end = max(end, max_end)
48+
margin = 0.2
49+
50+
def height(resource):
51+
if resource.capacity == 'inf':
52+
slots = 1.0
53+
else:
54+
slots = resource.capacity
55+
return (1.0-2*margin) / slots
56+
57+
for r in task.resources:
58+
y = (
59+
all_resources.index(r) +
60+
margin +
61+
height(r) * max(0, (resources[r] - 1))
62+
)
63+
ax.add_patch(
64+
patches.Rectangle(
65+
(start, y), # (x,y)
66+
end - start, # width
67+
height(r), # height
68+
facecolor=color
69+
)
70+
)
71+
72+
strips_colors = itt.cycle([(1, 1, 1), (1, 0.92, 0.92)])
73+
for i, color in zip(range(-1, len(all_resources) + 1), strips_colors):
74+
ax.fill_between(
75+
[0, 1.1 * max_end], [i, i], y2=[i + 1, i + 1], color=color)
76+
77+
N = len(all_resources)
78+
ax.set_yticks(np.arange(N) + 0.5)
79+
ax.set_ylim(-max(1, int(0.4 * N)), max(2, int(1.5 * N)))
80+
ax.set_yticklabels(all_resources)
81+
ax.legend(ncol=3, fontsize=8)
82+
ax.set_xlabel("time (a.u.)")
83+
ax.set_xlim(0, 1.1 * max_end)
84+
85+
return fig, ax
86+
87+
88+
# The following compute job trees
89+
90+
91+
def _compute_depth(node, graph):
92+
parents = graph.predecessors(node)
93+
return (0 if (parents == []) else
94+
1 + max([_compute_depth(n, graph) for n in parents]))
95+
96+
97+
def _tree_layout(graph):
98+
depth_dict = {
99+
node: _compute_depth(node, graph)
100+
for node in graph.nodes()
101+
}
102+
nodes_per_level = [
103+
[
104+
node
105+
for node in graph.nodes()
106+
if depth_dict[node] == depth
107+
]
108+
for depth in range(max(depth_dict.values()) + 1)
109+
]
110+
111+
positions = {}
112+
for level, level_nodes in enumerate(nodes_per_level):
113+
level_width = len(level_nodes)
114+
for i, node in enumerate(level_nodes):
115+
x_position = 8 * (i + 1.0) / (level_width + 1)
116+
y_position = -level
117+
positions[node] = (x_position, y_position)
118+
return positions
119+
120+
121+
def plot_dependency_graph(elements, mode="circle", figsize=(8, 5), ax=None):
122+
123+
import networkx as nx
124+
125+
if ax is None:
126+
fig, ax = plt.subplots(1, figsize=figsize)
127+
edges = [
128+
(e1.id, e2.id)
129+
for e2 in elements
130+
for e1 in e2.parents
131+
]
132+
graph = nx.DiGraph(edges)
133+
if mode == "circle":
134+
pos = nx.graphviz_layout(graph, prog='twopi')
135+
elif mode == "tree":
136+
pos = _tree_layout(graph)
137+
nx.draw(graph, pos, with_labels=False, arrows=True, node_size=20,
138+
alpha=0.5, node_color="blue", ax=ax)
139+
for w in elements:
140+
if w.due_time is not None:
141+
pass
142+
143+
return ax

Diff for: egfscheduler/tools.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
def gap_constraints(task1_name, task2_name, min_gap=None, max_gap=None):
2+
if max_gap is None:
3+
# only a min gap has been provided
4+
return lambda tasks: (
5+
(get_task_by_name(tasks, task1_name) +
6+
get_task_by_name(tasks, task1_name).duration +
7+
min_gap) > get_task_by_name(tasks, task2_name)
8+
)
9+
elif min_gap is None:
10+
# only a max gap has been provided
11+
return lambda tasks: (
12+
(get_task_by_name(tasks, task1_name) +
13+
get_task_by_name(tasks, task1_name).duration +
14+
max_gap) < get_task_by_name(tasks, task2_name)
15+
)
16+
else:
17+
return [
18+
gap_constraints(task1_name, task2_name, min_gap=min_gap),
19+
gap_constraints(task1_name, task2_name, max_gap=max_gap)
20+
]
21+
22+
23+
def get_task_by_name(tasks_list, name):
24+
tasks = [
25+
task for task in tasks_list
26+
if task.name == name
27+
]
28+
if len(tasks) != 1:
29+
raise ValueError("found %d entries for task name %s" % (
30+
len(tasks), name
31+
))
32+
return tasks[0]

0 commit comments

Comments
 (0)