Coder Social home page Coder Social logo

pyschedule's Introduction

pyschedule

pyschedule is python package to compute resource-constrained task schedules. Some features are:

  • precedence relations: e.g. task A should be done before task B
  • resource requirements: e.g. task A can be done by resource X or Y
  • resource capacities: e.g. resource X can only process a few tasks

Previous use-cases include:

  • school timetables: assign teachers to classes
  • beer brewing: assign equipment to brewing stages
  • sport schedules: assign stadiums to games

A simple pyschedule scenario where three houshold tasks need to get assigned to two persons, Alice and Bob:

from pyschedule import Scenario, solvers, plotters, alt

# the planning horizon has 10 periods
S = Scenario('household',horizon=10)

# two resources: Alice and Bob
Alice, Bob = S.Resource('Alice'), S.Resource('Bob')

# three tasks: cook, wash, and clean
cook = S.Task('cook',length=1,delay_cost=1)
wash = S.Task('wash',length=2,delay_cost=1)
clean = S.Task('clean',length=3,delay_cost=2)

# every task can be done either by Alice or Bob
cook += Alice | Bob
wash += Alice | Bob
clean += Alice | Bob

# compute and print a schedule
solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.025493860244750977
INFO: objective = 1.0
[(clean, Alice, 0, 3), (cook, Bob, 0, 1), (wash, Bob, 1, 3)]

We can also plot the schedule as a GANTT-chart and write it to a file:

plotters.matplotlib.plot(S,img_filename='pics/household.png')

png

There are example notebooks here and simpler examples in the examples folder. Install it with pip:

pip install pyschedule

Limits

Note that pyschedule aims to be a general solver for small to medium-sized scheduling problems. A typical scenario that pyschedule consists of 10 resources and 100 tasks with a planning horizon of 100 periods. If your requirements are much larger than this, then an out-of-the box solution is hard to obtain. There are some ways to speed-up pyschedule (e.g. see task groups and solver parameters). It is also possible to build heuristics on top of pyschedule to solve large-scaled scheduling problems.

How to start

When creating a scenario using pyschedule, the following imports are recommended:

from pyschedule import Scenario, solvers, plotters, alt

This allows the creation of a scenario:

S = Scenario('hello_world',horizon=10)

This scenario is named hello_world and has a time horizon of 10 periods. The granularity of the periods depends on your problem, e.g. a period could be an hour, a week, or a day. However, having far more than 100 periods makes the computation of a schedule quite hard. Some tricks to reduce the number of periods are:

  • Remove periods which are not used, like hours during the night.
  • Move to a higher granularity, e.g. try a granularity of 2 hours instead of 1 hour and round tasks up if necessary.

We need at least one resource in a scenario:

R = S.Resource('R')

It is convenient to have identical resource and variable names, like R. During each period, some task can be schedule this period. A resource can be anything from a person to an object like a machine in a factory. It is only required that a resource can be used by at most one task in every period.

Next we add a task to the scenario:

T = S.Task('T',length=1,delay_cost=1)

This task has length 1, that is, it requires only 1 period to finish. Since 1 is the default length of a task, we would not have to set this explicitely. Moreover, we set the delay cost to 1, that is, delaying this job for one period increases the cost of a schedule by 1, which motivates to finish this task as early as possible.

We define that task T requires resource R as follows:

T += R

Then we compute and print a schedule as follows:

solvers.mip.solve(S,msg=0)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.014132976531982422
INFO: objective = 0.0
[(T, R, 0, 1)]

The output first shows the time required to solve the problem. Also the objective is plotted. Since the cost of this schedule is only the delay cost of task T, which is schedule in period 0, the total cost is 0 as well. The standard way to present a schedule is a list of task-resource pairs with time requirements. E.g. the output above says that task T should be scheduled on resource R from period 0 to 1.

Costs

It is not necessary to define cost in a scenario. In this case, a solver will simply try to find a feasible schedule. Not defining any cost will sometimes even speed up the computation. However, in most scenarios, setting at least some delay cost makes sense.

Delay Cost

We set the delay cost of a task to 1 as follows:

T = S.Task('T',delay_cost=1)

This means that if this task is scheduled in period 0, then there will no delay cost, if it is schedule in period 1, there will be total cost 1 and so on. Hence, it makes sense to schedule this task as early as possible. Note that delay cost can also be negative, in which case a task will be pushed to the end of a schedule. Also note that a task with a higher delay cost is more likely to be scheduled earlier if there are no other constraints that are preventing this. The default delay cost is None.

Schedule Cost

Schedule cost can be used for optional tasks, that is, we provide some positive or negative reward if a task is scheduled:

from pyschedule import Scenario, solvers, plotters, alt
S = Scenario('schedule_cost',horizon=10)
R = S.Resource('R')

# not setting a schedule cost will set it to None
T0 = S.Task('T0',length=2,delay_cost=1)
# setting the schedule cost of T1 to -1
T1 = S.Task('T1',length=2,delay_cost=1,schedule_cost=-1)

T0 += R
T1 += R
solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.016648054122924805
INFO: objective = 0.0
[(T0, R, 0, 2)]

In the schedule above, scheduling task T1 with schedule cost -1 would decrease the total cost by 1, but then we would have to schedule both tasks T0 and T1, and hence one of them would have to start in period 2. This would result an additional delay cost of 2. Consequently, it makes more sense not to schedule T1.

Resource Cost

Using a resource for some periods might imply additional resource cost:

from pyschedule import Scenario, solvers, plotters, alt
S = Scenario('resource_cost',horizon=10)

# assign a cost per period of 5
R = S.Resource('R',cost_per_period=5)

T = S.Task('T',length=2,delay_cost=1)
T += R
solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.01111602783203125
INFO: objective = 10.0
[(T, R, 0, 2)]

The total cost of the computed schedule is 5 although the single task is scheduled in the first period. This is due to the fact that scheduling any task costs 5 on resource R.

Task and Resource Lists

To simplify the definition of tasks, it is possible to define task lists:

from pyschedule import Scenario, solvers, plotters, alt
S = Scenario('many_tasks',horizon=10)

# create 5 tasks of the same type
T = S.Tasks('T',num=5,length=1,delay_cost=1)

print(T)
[T0, T1, T2, T3, T4]

We created 5 tasks of length 1 and delay cost 1. The index of the tasks is padded to the end of the given task name. Therefore, avoid task names ending with digits. Note that it would also be possible to create all tasks separately. But if they are similar, this simplifies the definition of scheduling problems. Finally, we can similarly define lists of resources:

from pyschedule import Scenario, solvers, plotters, alt
S = Scenario('many_resources',horizon=10)

# create 5 resources of the same type
R = S.Resources('R',num=5)

print(R)
[R0, R1, R2, R3, R4]

Resource Assignment

It is possible to assign multiple resources to a task, either we define that one of these resources is required or all:

from pyschedule import Scenario, solvers, plotters, alt
S = Scenario('resources_assignment',horizon=10)

R = S.Resources('R',num=2)
T = S.Tasks('T',num=2,delay_cost=1)

# T0 requires either resource R0 or R1
T[0] += R[0] | R[1]

# T1 requires resources R0 and R1
T[1] += R[0], R[1]

# print the resources requirement
print(T[0].resources_req)
print(T[1].resources_req)
[R0|R1]
[R0, R1]

Note that if we have a list of resources, like above, we can also use the alt-operator:

# T0 requires any of the resources
from pyschedule import alt
T[0] += alt(R)

# T1 requires all of the resources
T1 += R

Now we can solve this scenario:

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.05312514305114746
INFO: objective = 1.0
[(T0, R0, 0, 1), (T1, R0, 1, 2), (T1, R1, 1, 2)]

Therefore, T0 is scheduled on resource R0 in period 0 and T1 on resources R0 and R1 in period 1.

Resource Dependencies

It is often necessary to ensure that two tasks select the same resources:

from pyschedule import Scenario, solvers, plotters, alt
S = Scenario('resources_dep',horizon=10)

R = S.Resources('R',num=2)
T = S.Tasks('T',num=2,delay_cost=1)

# assign all resources to both resources
T += alt(R)

# if T[1] is assigned to any resource in R, then also T[0]
T[0] += T[1] * R
	
# plot the resource dependencies of task T0
print(T[0].tasks_req)
[T1*R0, T1*R1]

Now we can solve this scenario

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.02900981903076172
INFO: objective = 1.0
[(T1, R0, 0, 1), (T0, R0, 1, 2)]

It would be better to distribute the two tasks to the two resources. However, due to the defined resource dependencies, they must be assigned to the same one.

Restricting Periods

We can restrict the periods when a job can be scheduled or when resource is available:

from pyschedule import Scenario, solvers, plotters, alt
S = Scenario('periods',horizon=10)

# restrict the periods to 2 and 3
T = S.Task('T', length=1, periods=[3,4])

# restrict the periods to the range 1..3
R = S.Resource('R', periods=range(1,4))
T += R
solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.04649972915649414
INFO: objective = 0.0
[(T, R, 3, 4)]

Clearly, due to the periods restrictions, the only possible period to schedule task T is 3.

Bounds

Another way to restrict the periods when a task can be scheduled are bounds:

from pyschedule import Scenario, solvers, plotters, alt
S = Scenario('bounds',horizon=10)
T = S.Task('T', length=1, delay_cost=1)
R = S.Resource('R')
T += R

# add the constraints that T needs to get schedule after period 1 but before 5
S += T > 1, T < 5

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.03368401527404785
INFO: objective = 1.0
[(T, R, 1, 2)]

This contraint is a lax bound, that is, task T can be schedule in any point after period 1. If we want to enforce when exactly T is scheduled, we can use a tight bound. E.g. to force T to be schedule exactly after period 1, we can write:

S += T >= 1

Precedences

Tasks often need to get scheduled in a certain order:

from pyschedule import Scenario, solvers, plotters
S = Scenario('lax_precedence',horizon=10)
R = S.Resource('R')
T = S.Tasks('T',num=2,length=1,delay_cost=1)
T += R

# give T0 a higher delay cost
T[0].delay_cost = 2
# add a precedence constraint to ensure that it is still scheduled one period after T1 finishes
S += T[1] + 1 < T[0] 

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.019501924514770508
INFO: objective = 4.0
[(T1, R, 0, 1), (T0, R, 2, 3)]

Since task T0 is delayed two periods, we get a total delay cost of 4. If we would not have the precedence constraint, we could schedule T0 first and only get delay cost 1. Note that the + 1 is optional.

We call this a lax precedence constraint. Similarly to tight bounds, tight precedence constraints additionally ensure that jobs are executed directly after each other:

from pyschedule import Scenario, solvers, plotters
S = Scenario('tight_precedence',horizon=10)
R = S.Resource('R')
T = S.Tasks('T',num=2,length=1,delay_cost=2)
T += R

# give T0 a negative delay cost
T[0].delay_cost = -1 
# ensure that T[0] is scheduled exactly two periods after T[1]
S += T[1] + 2 <= T[0] 

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.016117095947265625
INFO: objective = -3.0
[(T1, R, 0, 1), (T0, R, 3, 4)]

Since T0 has negative delay cost, it would be pushed to the end of the schedule, but the tight precedence constraint ensures that it is scheduled two periods after T1 finishes. If the delay cost of T1 would be smaller than T0, than both tasks would be pushed to the end of the schedule.

Conditional Precedences

It is often required that precedence constraints are only applied if two tasks are assigned to the same resource, e.g. if we want to ensure that a certain task is the last one that runs on some resource:

from pyschedule import Scenario, solvers, plotters, alt
S = Scenario('cond_precedence',horizon=10)
R = S.Resources('R',num=2)

T = S.Task('T',length=1,delay_cost=1)
T_final = S.Tasks('T_final',num=2,length=1,delay_cost=1)
T_final[0] += R[0]
T_final[1] += R[1]
T += alt(R)

# conditional precedences
S += T * R[0] < T_final[0]
S += T * R[1] < T_final[1]

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.040048837661743164
INFO: objective = 1.0
[(T, R0, 0, 1), (T_final1, R1, 0, 1), (T_final0, R0, 1, 2)]

The first conditional precedence implies that if task T is scheduled on R[0], then T_final[0] is scheduled afterwards. Therefore, it is allowed that T_final[1] is scheduled in the same period as T since T is not scheduled on R[1].

Capacities

Capacity constraints can be used to restrict the number tasks which are executed during a certain time period:

from pyschedule import Scenario, solvers, plotters
S = Scenario('capacities',horizon=10)
R = S.Resource('R')
T = S.Tasks('T',num=4,length=1,delay_cost=1)
T += R

# capacity constraint to limit the number of tasks until period 5 to 3
S += R[0:5] <= 3

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.015291213989257812
INFO: objective = 8.0
[(T3, R, 0, 1), (T0, R, 1, 2), (T1, R, 2, 3), (T2, R, 5, 6)]

Due to the capacity constraint, one task is scheduled after period 5. If not defined otherwise, the capacity constraint is applied to the lengths of the task. That is, the sum of lengths of tasks before period 5 is required to be at most 3. We can make this more explicit by writing:

S += R['length'][0:5] <= 3

Finally, if we want to bound the maximum instead of the sum, we can write:

S += R['length'][0:5].max <= 3

Non-unit Tasks

Cases where task lengths are larger than one deserve a special treatment:

from pyschedule import Scenario, solvers, plotters
S = Scenario('capacities',horizon=10)
R = S.Resource('R')

# task with non-unit length
T = S.Task('T',length=4,delay_cost=1)
T += R

S += R[0:5] <= 3

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.022754907608032227
INFO: objective = 2.0
[(T, R, 2, 6)]

Task T has to start in period 2 because of the capacity constraint. This is possible because the length of the part of this task which lies within the capacity constraint is 3. Specifically, the part scheduled in periods 2,3 and 4. This holds in general, a task contributes to a standard capacity constraint proportionally to how much it overlaps with the capacity constraint. This generalizes to user-defined task attributes as described in the next section.

User-Defined Task Attributes

We can apply capacity constraints to all task attributes, not just the task lengths, but also user-defined ones:

from pyschedule import Scenario, solvers, plotters
S = Scenario('capacities_myattribute',horizon=10)
R = S.Resource('R')

# define the additional property named myproperty uniformly as 1
T = S.Tasks('T',num=4,length=1,delay_cost=1,myattribute=1)
# set it to 0 for the first task
T[0].myattribute = 0
T += R

# the sum of myproperty must be smaller than 3 until period 5
S += R['myattribute'][0:5] <= 3

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.018787145614624023
INFO: objective = 6.0
[(T3, R, 0, 1), (T0, R, 1, 2), (T1, R, 2, 3), (T2, R, 3, 4)]

Since T[0] does not add anything to the sum of the myattribute-values before period 5, all tasks can be scheduled before this period.

Bounding Differences

The default way to aggregate within the range of a capacity constraint is to summarize. On the other hand, if we want to ensure that some attribute does not change too much over time, we can also restrict the sum of differences of this attribute:

from pyschedule import Scenario, solvers, plotters
S = Scenario('capacities_diff',horizon=10)
R = S.Resource('R')

T = S.Tasks('T',num=4,length=1,delay_cost=1,myattribute=1)
T[0].delay_cost = 2
T[0].myattribute = 0
T[1].delay_cost = 2
T[1].myattribute = 0
T += R
 
# limit the sum of differences of myattribute to 1
S += R['myattribute'].diff <= 1

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.05473184585571289
INFO: objective = 11.0
[(T3, R, 0, 1), (T2, R, 1, 2), (T0, R, 2, 3), (T1, R, 3, 4)]

Note that if we do not define the range of a capacity constraint like above, then the constraint is applied to the complete time horizon. In the scenario above, it would be advantageous to schedule tasks T[0] and T[1] as early as possible, since they have a higher delay cost. However, if would schedule them in periods 0 and 1, respectively, and directly afterwards T[2] and T[3], then myattribute would first increase by 1 in period 2 and afterwards decrease again by 1 in period 4, resulting in a sum of differences of 2.

The .diff-capacity constraint limits the sum of increases and decreases. If we only want to limit the increases or decreases, then we can use .diff_up or .diff_down, respectively.

Combining Constraints

We can combine capacity constraints doing natural arithmetic:

from pyschedule import Scenario, solvers, plotters
S = Scenario('capacities_arithmetic',horizon=10)
R = S.Resource('R')

T = S.Tasks('T',num=4,length=1,delay_cost=1,myattribute=1)
T += R
 
# add two capacities
S += R['myattribute'][:3] + R['myattribute'][5:7] <= 1

solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.03471493721008301
INFO: objective = 14.0
[(T3, R, 0, 1), (T2, R, 3, 4), (T1, R, 4, 5), (T0, R, 7, 8)]

Since one task is schedule in period 0, we cannot schedule any more tasks in periods 0 to 2 or in periods 5 to 6 . Therefore, we squeeze in two tasks in periods 3 and 4 and one task in period 7.

Task Groups

There are often task redundancies in a planning project, e.g. there might be a group of tasks which are interchangeable. That is, they could be swapped in the schedule without changing its cost or feasibility. Given this information in the is_group-attribute, this can be exploited by the solver to often drastically speed-up the computation:

from pyschedule import Scenario, solvers, plotters
S = Scenario('task_groups',horizon=10)
R = S.Resource('R')

# these tasks are interchangeable
T = S.Tasks('T',num=10,length=1,delay_cost=1,is_group=True)
T += R
 
solvers.mip.solve(S,msg=1)
print(S.solution())
INFO: execution time for solving mip (sec) = 0.01534271240234375
INFO: objective = 45.0
[(T0, R, 0, 1), (T1, R, 1, 2), (T2, R, 2, 3), (T3, R, 3, 4), (T4, R, 4, 5), (T5, R, 5, 6), (T6, R, 6, 7), (T7, R, 7, 8), (T8, R, 8, 9), (T9, R, 9, 10)]

Running this with setting is_group=False only slightly increases the running time, but there are scenarios where this difference is much more significant:

INFO: execution time for solving mip (sec) = 0.025635719299316406
INFO: objective = 45.0
[(T7, R, 0, 1), (T0, R, 1, 2), (T4, R, 2, 3), (T2, R, 3, 4), (T5, R, 4, 5), (T6, R, 5, 6), (T1, R, 6, 7), (T9, R, 7, 8), (T8, R, 8, 9), (T3, R, 9, 10)]

CAUTION: combining task groups with capacities with resource dependencies might not work in some cases.

Solver Parameters

The default pyschedule backend is a time-indexed mixed integer formulation (MIP). There are the following parameters:

  • msg: show info on/off (default is 0)
  • time_limit: limit the solving time in seconds (default is None)
  • ratio_gap: stop the solving process when this integrality gap is reached, e.g. 1.2 stands for 20% gap to optimality (default is None)
  • random_seed: the random seed used by the solver (default is 42)
  • kind: the Integer Programming backend to use. The default is CBC which comes preinstalled with package pulp. If SCIP is installed (command scip must be running), you can use SCIP. Finally, if you have CPLEX installed (command cplex must be running), you can use CPLEX

E.g. this could be used as follows:

solvers.mip.solve(S,kind='CPLEX',time_limit=60,random_seed=42,msg=1)

Plotter Parameters

The default pyschedule backend to plot a schedule is matplotlib. Some parameters are:

  • img_filename: write the plot as a .png-file (default is None)
  • fig_size: size of the plot (default is (15,5))
  • resource_height: the height of a resource in the plot (default is 1)
  • show_task_labels: show the labels of tasks in the plot (default is True)
  • hide_tasks: list of tasks not to plot (default is [])
  • hide_resources: list of resources to hide in the plot (default is [])
  • task_colors: a mapping of tasks to colors (default is empty dictionary)
  • vertical_text: write the task labels vertically (default if False)

E.g. this could be used as follows:

plotters.matplotlib.plot(S,img_filename='tmp.png',img_size=(5,5),hide_tasks=[T])

FINAL CAUTION: pyschedule is under active development, there might be non-backward-compatible changes.

Appendix

import sys
sys.path.append('src')

pyschedule's People

Contributors

crayster avatar ericsagnes avatar fderyckel avatar mthwrobinson avatar ppoile avatar samueletorregrossa avatar timnon avatar tpaviot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyschedule's Issues

Adding optional resources in loop

Hi,

I am attempting to assign a variable amount of optional resources to a task in a loop like so:

T += R[0] | R[1] | R[2] | ... | R[n]

However, adding R as an array creates multiple tasks. For example, this does not achieve the desired result:

T += R

A solution I came up with that works is by creating a string in a loop the is the command I would like to executue and the calling the exec() function like so...

exec_str = "T[i] +="
for i in range(len(R)):
    exec_str+=" R["+str(i)+"] "
    if (i != len(R) - 1):
        exec_str+= "|"
exec(exec_str)

This solution, however, seems pretty ugly to me. Anybody see any better alternatives?

Thanks,
Jess

File not found cpoptimizer.mod

When i traied first time use cpoptimizer.solve_docloud method everething working perfect. After few hours the same method starts report an error:
FileNotFoundError: [Errno 2] No such file or directory:
'...\lib\site-packages\pyschedule\solvers/cpoptimizer.mod'

Does Altmult work with >2 resources?

First off - this project is great and I've been having a lot of fun playing with it!

Trying to load/inject/cure two parts on two available molds - but the mold has to be the same for all three steps. I've written the resource like:

A1 = mold1a|mold1b; load_part1 += A1; inject_part1 += A1; cure_part1 += A1
A2 = mold2a|mold2b; load_part2 += A2; inject_part2 += A2; cure_part2 += A2
A3 = mold3a|mold3b; load_part3 += A3; inject_part3 += A3; cure_part3 += A3

However in the output I still get the two molds switching back and forth. Inject and Cure always happen on the same mold, but that may be due to the constraint:

S += inject_part1 <= cure_part1
S += inject_part2 <= cure_part2
S += inject_part3 <= cure_part3

However, inject doesn't have to happen immediately after load so I've specified:

S += load_part1 < inject_part1
S += load_part2 < inject_part2
S += load_part3 < inject_part3

Also important that the "molds" cant be used by another task until the "cure" is finished, regardless of any gaps...

Thanks!

CAPSLICE with DoCloud

Hi,

Thanks for a very useful tool.

My scheduling problem has people resources and machine resources. Some tasks require people and machines, others only require machines. Since people are limited to working during certain hours of the day, I have added CAPSLICE constraints to ensure that the capacity of each people resource is 0 during the night. It seems to work on Pulp but not on DoCloud. It looks like DoCloud accepts CAPSLICE constraints over the entire project but doesn't do anything about CAPSLICE constraints with defined start and end time. Can you give a little more info on the limitations of DoCloud?

Thanks,

Jonathan

Resource / Task names

the framework doesn't like names like: 'backend'. start and end cause runtime issues.

Slowdown between 0.2.25 and 0.2.30

I noticed a big slowdown between version 0.2.25 and version 0.2.30. I use my school's schedule as my benchmark.

Time on 0.2.25: <0.5s
Time on 0.2.30: ~7s

The data and the script I use is here: repo.
Log for 0.2.25/0.2.30: gist

How to not show pulp.mps files?

Hi everyone, i'm using this library on Windows - Anaconda - Python 3.5
I call the scheduler solver in a cycle and in every iteration the solver (i suppose pulp does it) open a window for just a moment and meanwhile in my current directory a "5396-pulp.mps" file is created and deleted immediately.
In the code i use the solver mip.solve_bigm, i tried to use also the default solver mip.solve, but nothing chenged. (in the problem i have constraints of type COND, the available solvers are limited.

I would know if it's possible not show the window and the file .mps opened by the solver.
Thanks you all for any help.
manzo1991

Constrains for a group of tasks

Hi there, first thing first, thanks a lot for your effort you put into this project!
First time I am playing with this type of thing, but I get stuck with one condition that I can not bring to work:
First I created three long task and was wondering how to make it that the tasks are automatically assigned to multiple people. I think this is not possible in a way that they share the work. So I splitted the tasks into group of tasks each one day long. This works well, the day task are shared between two persons.

But one group of development tasks (webback_d) should be scheduled AFTER the task "sysarch", but I can not bring it to work. Here is my code:

# Load pyschedule and create a scenario with ten steps planning horizon
from pyschedule import Scenario, solvers, plotters
S = Scenario('hello_pyschedule',horizon=50)


# Create some resources
Nick, Jim, Peter, Bob = S.Resource('Nick',size=1), S.Resource('Jim',size=1), S.Resource('Peter',size=1), S.Resource('Bob',size=1)


sysarch = S.Task('ARC',3,plot_color='#66FFff')
webfront_d = S.Tasks('GUI',num=10,length=1,is_group=True,plot_color='#6699ff')
webback_d = S.Tasks('SRV',num=15,length=1,is_group=True,plot_color='#FF99ff')
   
# Assign tasks to resources
webfront_d += Jim
webback_d += Peter | Nick
sysarch += Nick | Bob

#Here I try to define that all tasks in the group webback_d should only happen after the task sysarch is completed:
for task in webback_d:
    S += sysarch < task

solvers.mip.solve(S,msg=1)

print(S.solution())
plotters.matplotlib.plot(S,show_task_labels=True,fig_size=(10,5))

optional breaks in timetable & prevent end-of-day overrun

I have a horizon of 40, corresponding to 5 days of 8 hours.
There are tasks with length = 1, 2, 3 or 4 hours.
Tasks can be randomly placed during daytime, but they cannot overrun the end of day, and they must remain 'whole'

How do I model this constraint?

Prevent simultaneous tasks

Thank you @timnon for this interesting project.

In the workflow I've been trying to represent using pyschedule, I have two tasks, let's say T1 and T2, that can't be executed at the same time (i.e. ran parallel) and are unordered (two possible solutions are then T1 then T2 or T2 then T1). How is it possible to represent such a constraint ?

Currently, If I don't specify any constraint, the solver results in a parallel execution. In my mind, the constraint could be written as:

S+ = (T1 < T2) or (T2 < T1)

but this is not interpreted as I want by pyschedule. Any advice ?

Help needed

I have 5 days, each days consists of 5 hours, 6 courses that each require 3 hours, but no 2hours can be on the same day
Any idea on how to model this?
Here is what I would start with:

s = Scenario('S_trial01', horizon=25)
class = s.Resource('Class_6')
courseA = s.Task('Course_A', 3)
courseB = s.Task('Course_B', 3)
courseC = s.Task('Course_C', 3)
courseD = s.Task('Course_D', 3)
courseE = s.Task('Course_E', 3)
courseF = s.Task('Course_F', 3)

But then I am not sure how to add the constraints of not having twice the same course on one day.

Any suggestion?

Variable length task?

Hi,

First of all, thanks for this wonderful piece of code!
I've managed to modelize almost all my needs except one case. Is there a way to either:

  • have a variable task that can be dynamically extended to the beginning of its following?
  • block a resource until the next task is scheduled?

To understand my scenario, here's the use case:

  • we brew (1 day)
  • we ferment (~15 days)
  • and we bottle condition as soon as we're available

[Brew/R=brewhouse] <= [Ferment/R=Tank1] <..[variable length task/R=Tank1]..< [Bottle condition/R=Tank1+Capper]

The problem is that sometimes, we are busy or it's sunday and we want to wait a few days before bottle conditioning. How would you achieve that? Thank you!

Representing maximum duration

I'd like to represent the fact that a resource is available only 4 hours a day. That is to say that the total amount of tasks ran by this resource has to be between 8.00 AM and 12.00, or between 8.30 and 12.30, or 13.30 17.30 etc. What kind of constraint should I add ? If 1 is "one hour", then in pseudo code, the constraint I have in mind would be

S += (end_of_last_task - start_of_first_task) < =4

Is there something similar that I could use in the list of resource constraints ?

print time it takes?

Hi Tim,

first of all thanks for sharing pyschedule with us!
I have a question. Is there any way to print the time the scheduled tasks take until they're all done?
Best

Esther

Example in readme broken ?

When I run the example in the readme with the Github version of pyschedule.

TypeError                                 Traceback (most recent call last)
<ipython-input-3-9c0022404aa0> in <module>()
      1 # Load pyschedule and create a scenario with ten steps planning horizon
      2 from pyschedule import Scenario, solvers, plotters
----> 3 S = Scenario('hello_pyschedule',horizon=10)
      4 
      5 # Create two resources

TypeError: __init__() got an unexpected keyword argument 'horizon'

And if I remove horizon it complains that solver.mip doesn't exist. Maybe update the example in the readme ?

Schedule Design question

Hi @timnon
I'm posting this here as I'm not sure where else to ask;
I have a design question:
Can we have 2 type of tasks but constraints on only one of the type.
Here is the scenario.
Let's say a teacher can perform up to 6 teaching task but no more than 3 in a row on a 8 periods time. That's ok to model.
Now we need to add a department meeting task or lunch for both R1 and R2
So this is OK
Teaching, Teaching, Meeting, Teaching, , Teaching, Teaching, Teaching

And this is OK
Teaching, Teaching, Teaching, Meeting, Teaching, , Teaching

But this is NOT OK:
Teaching, Teaching, Teaching, Teaching, , Meeting, Teaching, Teaching

Basically, how can we model:
for any 4 consecutive periods:
it should contain either
no more than 3 tasks or
it should include the task "meeting/lunch"?

Manufacturing order Problem

My scenario is

  1. I have several manufacturing order's (consider 100+)

  2. each manufacturing order have preferance like
    eg(i have group's of manufacturing-order of 5+ ('MO1','MO2','MO3','MO4','MO5') and 'MO3' should be proceed before 'MO2' and 'MO2' should be proceed before 'MO1') some thing like this

  3. and each group of manufacturing need to procced before other group
    like eg. group1 should be proceed before group2 like this

  4. each manufacturing order have 4+ workorder

  5. each work-order need to process on particular machine

  6. each work order have precedence like eg. 1st should be procced before 2nd and 2nd should be proceed before 3rd something like this

i have manage to give precednce to workorder's

but i am not able to give precedece to manufacturing order and manufacturing order's
group's

following is my snipet

can someone correct this

you can change logic as per you need.

thanks

S = Scenario('Schedule_1', horizon=23)

for mo in all_mo:

previous_task=''
for workorder in mo_obj.workcenter_lines:
    
    current_task=S.Task(str(workorder.id), length=int(workorder.hour)')
    
    #assign precedence to task
    if not previous_task=='':
        S+=previous_task < current_task

    #precedence block-end
    
    workcenter_id=str(workorder.workcenter_id.id)
    resource_list=S.resources()

    resource_exist=False
    existing_resource=''
    for item in resource_list:
        resource_exist=False
        if workcenter_id==item.name:
            existing_resource=item
            resource_exist=True
            break
        
        
    if not resource_exist:                    
        resource = S.Resource(workcenter_id)
        current_task+=resource
    else:
        current_task+=existing_resource

    previous_task=current_task

forcing 2 tasks to be at the same time

Hi @timnon ,
First, thank you so much for making pyschedule and letting us enjoy it.

I was wondering if it is possible to create the following scenario.
T1 += R1
T2 +=R2

Now I want T1 and T2 to be scheduled at the same time. How do we go about that?

I know I can do
s += T1 < T2
or even
s += T1 <= T2

But it seems I cannot do
s += T1 == T2

Would there be a work around for that?

Thanks in advance.

Best,

schedule_cost doesnt work with T >= 0 constraint

In the following example, T1 should not be scheduled, but only T2 in slot 0:

#! /usr/bin/python
import sys
sys.path.append('../src')
import pyschedule

# get input size
S = pyschedule.Scenario('test',horizon=10)

R = S.Resource('R')
T1 = S.Task('T1',length=1,schedule_cost=100)
T2 = S.Task('T2',completion_time_cost=1)
S += T1 >= 0
S += T1 < T2
T1 += R
T2 += R

if pyschedule.solvers.mip.solve(S,msg=1,kind='CBC'):
	pyschedule.plotters.matplotlib.plot(S,color_prec_groups=False)
else:
	print('no solution found')

Not Fulltime

I am trying to schedule multiple projects and resources. I had a problem with assigning some percent of the employee to a given task.

I mean lenght of a task and effort of a task can be different. How can I assign some of the employee's effort to a task?

Thanks

Budget as another constraint

In addition to the upper bound on the time span, can I also have the budget as a constraint? Resources have costs, and length of task can vary depending on the kind of resource (experienced/expensive) used.

Thanks
Jay

How to handle big task across holidays?

Hello there,
I'm not sure whether CAPSLICE is supported by mip.solve, it seems YES according to the test result.

Assume a simple scenario:

S = Scenario('test1', horizon=10)

# only one task, consuming 7 days
T1 = S.Task('T1', 7)

# only one resource, not work in weekend
R1 = S.Resource('R1')
S += S['R1']['length'][5:7] <= 0

S['T1'] += S['R1']
solvers.mip.solve(S)

Expected result is:
[(T1, R1, 0, 9)]

but returned:
[(T1, R1, 0, 7)]

Any idea? Thank you very much!

pyschedule tsp-germany

Hi there,

Do you guys have an idea why this code (available here: https://programtalk.com/vs2/?source=python/13057/pyschedule/examples/tsp-germany.py) related to travelling salesman in Germany with pyschedule, does not work properly ?

Thanks.


import pyschedule
import math, sys

cities = '
Aachen 50.45 6.06\n
Aalen 48.51 10.06\n
Aller 52.56 09.12\n
Amberg 49.26 11.52\n
Amper 48.29 11.55\n
Ansbach 49.28 10.34\n
Arnsberg 51.24 08.05\n
Aschaffenburg 49.58 09.06\n
Aschersleben 51.45 11.29\n
Augsburg 48.25 20.20\n
Aurich 53.28 07.28\n
BadKissingen 50.11 10.04\n
Baden-Baden 48.44 08.13\n
Baden-Wurttemberg 48.20 08.40\n
Bamberg 49.54 10.54\n
Bautzen 51.10 14.26\n
Bavaria/Bayern 48.50 12.00\n
Bayern 48.50 12.00\n
Bayreuth 49.56 11.35\n
BergischGladbach 50.59 07.08\n
Berlin 52.30 13.25\n
Bernburg 51.47 11.44\n
Biberach 48.05 09.47\n
Beilefeld 52.01 08.33\n
BlackForest/Schwarzwald 48.30 08.20\n
Bochum 51.28 07.13\n
BohemianForest/Bohmerwald 49.08 13.14\n
Bohmerwald 49.08 13.14\n
Bonn 50.46 07.06\n
Borkum 53.34 06.40\n
Bottrop 51.31 06.58\n
Brandenburg/Neubrandenburg 53.33 13.1\n
Brandenburg 52.25 12.33\n
Brandenburg 52.50 13.00\n
Braunschweig 52.15 10.31\n
Bremen 53.04 08.47\n
Bremerhaven 53.33 08.36\n
Brocken 51.47 10.37\n
Brunswick/Braunschweig 52.15 10.31\n
Buxtehude 53.28 09.39\n
Celle 52.37 10.04\n
Chemnitz 50.51 12.54\n
Chiemsee 47.53 12.28\n
Coburg 50.15 10.58\n
Cologne/Koln 50.56 06.57\n
Constance/Konstanz 47.40 09.10\n
Cottbus 51.45 14.20\n
Crailsheim 49.08 10.05\n
Cuxhaven 53.51 08.41\n
Dachau 48.15 11.26\n
Darmstadt 49.51 08.39\n
Deggendorf 48.50 12.57\n
Dessau 51.51 12.14\n
Detmold 51.56 08.52\n
DeutshceBucht 54.15 08.00\n
Donauworth 48.43 10.47\n
Dortmund 51.30 07.28\n
Dresden 51.03 13.44\n
Duisburg 51.26 06.45\n
Duren 50.48 06.29\n
Dusseldorf 51.14 06.47\n
Eberswalde-Finow 52.50 13.49\n
Eder 51.12 09.28\n
Eifel 50.15 06.50\n
Eisenach 50.58 10.19\n
Elde 53.07 11.15\n
Elmshorn 53.43 09.40\n
Emden 53.21 07.12\n
Ems 53.20 07.12\n
Erfurt 50.58 11.02\n
Erlangen 49.36 11.00\n
Erzgebirge 50.27 12.55\n
Essen 51.28 07.02\n
Esslingen 48.44 09.18\n
Fehmarn 54.27 11.07\n
Flensburg 54.47 09.27\n
Fohr 54.43 08.30\n
Forst 51.45 14.37\n
Frankfurt,Brandenburg 52.20 14.32\n
Frankfurt,Hessen 50.07 08.41\n
FrankischeAlb 49.10 11.23\n
Freiburg 47.59 07.51\n
Freising 48.24 11.45\n
Friedrichshafen 47.39 09.30\n
Fulda 50.32 09.40\n
Fulda 51.25 09.39\n
Furstenwalde 52.22 14.03\n
Furth 49.28 10.59\n
Garmisch-Patenkirchen 47.30 11.06\n
Geesthacht 53.26 10.22\n
Gelsenkirchen 51.32 07.06\n
Gera 50.53 12.04\n
Giessen 50.34 08.41\n
Goppingen 48.42 09.39\n
Gorlitz 51.09 14.58\n
Gosalr 51.54 10.25\n
Gotha 50.56 10.42\n
Gottingen 51.31 09.55\n
Greifswald 54.05 13.23\n
Greiz 50.59 12.10\n
GrosserArber 49.06 13.08\n
Gustrow 53.47 12.10\n
Gutersloh 51.54 08.24\n
Hagen 51.21 07.27\n
Halberstadt 51.54 11.03\n
Halle 51.30 11.56\n
Hamburg 53.33 09.59\n
Hameln 52.06 09.21\n
Hamlin/Hamein 52.06 09.21\n
Hamm 51.40 07.50\n
Hanau 50.07 08.56\n
Hannover 52.22 09.46\n
Hanover/Hannover 52.22 09.46\n
Harz 51.38 10.44\n
Havel 52.50 12.03\n
Heidelberg 49.24 08.42\n
Heilbronn 49.09 09.13\n
Helgoland 54.10 07.53\n
Heligoland/Helgoland 54.10 07.53\n
HeligolandB/DeutscheBucht 54.15 08.00\n
Herford 52.07 08.39\n
Herne 51.32 07.14\n
Hesse/Hessen 50.30 09.00\n
Hessen 50.30 09.00\n
Hildesheim 52.09 09.56\n
Hof 50.19 11.55\n
HoherRhon/Rhon 50.24 09.58\n
Hoxter 51.46 09.22\n
Hoyerswerda 51.26 14.14\n
Hunsruck 49.56 07.27\n
Idar-Oberstein 49.43 07.16\n
Iller 48.23 09.58\n
Ingolstadt 48.46 11.26\n
Isar 48.48 12.57\n
Itzehoe 53.55 09.31\n
Jena 50.54 11.35\n
Jura/SchwabischeAlb 48.20 09.30\n
kaiserslautern 49.26 07.45\n
Karl-Marx-stadt/Chemnitz 50.51 12.54\n
Karlsruhe 49.00 08.23\n
Kassel 51.18 09.26\n
Kempten 47.45 10.17\n
Kiel 54.19 10.08\n
KielCanal/Nord-Ostsee-Kanal 54.12 09.32\n
KielerBucht 54.35 10.25\n
Koblenz 50.21 07.36\n
Koln 50.56 06.57\n
Konstanz 47.40 09.10\n
Krefeld 51.20 06.33\n
Lahn 50.19 07.37\n
Landshut 48.34 12.08\n
Lauchhammer 51.29 13.47\n
Lech 48.43 10.56\n
Leer 53.13 07.26\n
Leine 52.43 09.36\n
Leipzig 51.18 12.22\n
Limburg 50.22 08.04\n
Lingen 52.31 07.19\n
Lippe 51.39 06.36\n
LowerSaxony=Niedersachsen 52.50 09.0\n
Lubeck 53.52 10.40\n
Luckenwalde 52.05 13.10\n
Ludwigsburg 48.53 09.11\n
Ludwigshafen 49.29 08.26\n
Luneburg 53.15 10.24\n
LuneburgHeath=LuneburgerHeide 53.10 10.12\n
LuneburgerHeide 53.10 10.12\n
LutherssstadtWittenberg 51.53 12.39\n
Magdeburg 52.07 11.38\n
Main 50.0 08.18\n
Mainz 50.01 08.14\n
MannheiM 49.29 08.29\n
Marburg 50.47 08.46\n
Mecklenburg 53.33 11.40\n
MeckelenburgerBucht 54.20 11.40\n
Meissen 51.09 13.29\n
Memmingen 47.58 10.10\n
Merseburg 51.22 11.59\n
Minden 52.17 0\n
Monchegladbach 51.11 06.27\n
Muhlhausen 51.12 10.27\n
Mulde 51.53 12.15\n
Mulheim 51.52 06.54\n
Munchen 48.08 11.34\n
Munchen-Gladbach/Monchengladbach 51.11 06.27\n
Muden 51.25 09.38\n
Munich/Munchen 48.08 11.31\n
Munster 51.58 07.37\n
Muritz 53.25 12.42\n
Naab 49.01 12.2\n
Naumburg 51.09 11.47\n
Neckar 49.27 08.29\n
Neumunster 54.04 09.58\n
Neunkirchen 49.20 07.09\n
Neuruppin 52.55 12.48\n
Neustrelitz 53.21 13.04\n
Niedersachsen 52.50 09.0\n
Nienburg 52.39 09.13\n
Nord-Ostsee-Kanal 54.12 09.32\n
Norderney 53.42 07.09\n
Norderstedt 53.42 10.01\n
NordfriesischeInsein 54.40 08.20\n
Nordhausen 51.30 10.47\n
Nordrhein-Westfalen 51.45 07.30\n
NorthFrisianIs./NordfriesischeInseln 54.40 08.20\n
NorthRhineWestphalia/Nordrhein-Westfalen 51.45 07.30\n
Nuremberg/Nurnberg 49.27 11.03\n
Nurnberg 49.27 11.03\n
Oberhausen 51.28 06.51\n
Offenbach 50.06 08.44\n
Offenburg 48.28 07.56\n
Oldenburg 53.09 08.13\n
Oranienburg 52.45 13.14\n
OreMts/Erzgebirge 50.27 12.55\n
Osnabruck 52.17 08.03\n
OstfriesischeInsein 53.42 07.0\n
Paderborn 51.42 08.45\n
Parchim 53.26 11.52\n
Passau 48.34 13.28\n
Peine 52.19 10.14\n
Pforzheim 48.52 08.41\n
Pirmasens 49.12 07.36\n
Plauen 50.30 12.08\n
Plockenstein 48.46 13.51\n
Potsdam 52.25 13.04\n
Puttgarden 54.30 11.10\n
Rathenow 52.37 12.19\n
Ravensburg 47.46 09.36\n
Recklinghausen 51.7 07.12\n
Regensburg 49.01 12.06\n
Reichenbach 50.37 12.17\n
Remscheid 51.11 07.12\n
Rendsburg 54.17 09.39\n
Reutlingen 48.29 09.12\n
Rhein-Main-Donau-Kanal 52.17 07.26\n
Rheine 52.17 07.26\n
Rheinland-Pfalz 50.0 07.0\n
Rhineland-Palatinate=Rheinland-Pfalz 50.0 07.0\n
Rhon 50.24 09.58\n
Riesa 51.17 13.17\n
Rostock 54.5 12.8\n
Rugen 54.22 13.24\n
Saale 51.56 11.54\n
Sachsen 50.55 13.10\n
Salzgitter 52.9 10.19\n
Salzwedel 52.52 11.10\n
Sassnitz 54.29 13.39\n
Sauerland 51.12 7.59\n
Saxony/Sachsen 50.55 13.10\n
Scjhleswig 54.31 9.34\n
SchwabischeAlb 48.20 9.30\n
Schwedt 53.3 14.16\n
Schweinfurt 50.3 10.14\n
Schwerin 53.36 11.22\n
Speyer 49.29 8.25\n
Stade 53.35 9.29\n
Trier 49.45 6.38\n
Tubingen 48.31 9.4\n
Tuttlingen 47.58 8.48\n
Uelzen 52.57 10.32\n
Ulm 48.23 9.58\n
Villingen-Schwenningen 48.3 8.26\n
Vgogelsberg 50.31 9.12\n
Wangerooge 53.47 7.54\n
Wasserkuppe 50.29 9.55\n
Weiden 49.41 12.10\n
Weimar 50.58 11.19\n
Werra 51.24 9.39\n
Weser 53.36 8.28\n
Wetzlar 50.32 8.31\n
Wismar 53.54 11.29\n
Wittenberge 53.0 11.45\n
Worms 49.37 8.21\n
Wuppertal 51.16 7.12\n
Wurzburg 49.46 9.55'

euclidean distance computation

def eucl_dist(orig,dest) :
return math.sqrt( (orig[0]-dest[0])**2 + (orig[1]-dest[1])**2 )

get cities table

cities_table = [ row.split(' ') for row in cities.split('\n') ]
cities_table = [ (city,float(lon),float(lat)) for city,lon,lat in cities_table ]
n = 10 # use only few cities to test, more cities take a long time #len(cities_table)
coords = { cities_table[i][0] : (cities_table[i][2],cities_table[i][1]) for i in range(n) }
cities = list(coords)

add coordinates of vitual start and end at the first city in list

start_city = cities_table[0][0]
coords['début'] = coords[start_city]
coords['fin'] = coords[start_city]

scenario and city tasks

S = pyschedule.Scenario('TSP_Germany')
T = { city : S.Task(city) for city in coords }
Car = S.Resource('Car')

the car has to pass every city

for city in coords:
T[city] += Car

make sure that the tour start and ends at start_city

S += T['début'] < { T[city] for city in coords if city != 'début' }
S += T['fin'] > { T[city] for city in coords if city != 'fin' }

add euclidean distances as conditional precedences

S += [ T[city] + int(eucl_dist(coords[city],coords[city_])) << T[city_]
for city in coords for city_ in coords if city != city_ ]

objective: minimze the end of the trip (multiply with 1 to turn into affine combination of tasks)

S += T['fin']*1

#if not pyschedule.solvers.mip.solve_bigm(S,time_limit=30,msg=1):

print('no solution found')

sys.exit()

S.use_makespan_objective()

pyschedule.plotters.matplotlib.plot(S,resource_height=1.0,show_task_labels=True,color_prec_groups=False)

plot tours

import pylab
sol = S.solution()
tour = [ coords[str(city)] for (city,resource,start,end) in sol if str(city) in coords ]
pylab.plot([ x for x,y in tour ],[ y for x,y in tour],linewidth=2.0,color='blue')

plot city names

for city in cities : pylab.text(coords[city][0], coords[city][1], city,color='black',fontsize=10)

pylab.title('TSP Germany')
pylab.show()

Schedule meetings given time-slot availabilities and who is needed at meetings

Can this be used to schedule meetings into time-slots where not all resources are available for a particular timeslot?

So far I've thought to model the meetings as tasks and the people as resources. And then say which resources (people) are needed for each meeting.

So far so good :) But I'm not sure how to model which periods some of the resources are not available? Is it perhaps the CAPSLICE parameter? Or might I approach it by modeling the time periods as resources as well?

Thanks,
James

Resources cost

Is there a way to set a cost per unit for resources such as the solver will minimize the execution cost of a schedule?

Use same resource for related tasks but only where possible

Hi there!

In the example we have

First remove the old resource to task assignments

green_paint -= Alice|Bob
green_post -= Alice|Bob
red_paint -= Alice|Bob
red_post -= Alice|Bob

Add new shared ones

green_resource = Alice|Bob
green_paint += green_resource
green_post += green_resource

red_resource = Alice|Bob
red_paint += red_resource
red_post += red_resource
run(S)

Is it possible to say that the same resource should manage related tasks but only where possible (i.e. make this a little more lax).

For example. Alice would ideally postprocess the bike she also painted but is willing to postprocess the bike that Bob painted if necessary.

Not solving relatively easy problem?

Hi, I'm scheduling bookings for properties in my code, and it is setup to run a really simple problem where two bookings are scheduled at the same time and we just disperse them between the two resources (units) that are available to put them in.

However, pyscheduler says there are no solutions.

Here is the code that I am executing:

`solvers.mip.solve(S, msg=1)

# Print the solution
print(S.solution())`

And here is what is printed to the terminal:

`###############################################

SCENARIO: resolve_double_booking / horizon: 150

OBJECTIVE: None

RESOURCES:
CedarR312
CedarR416

TASKS:
xAAP : CedarR312
bAAN : CedarR312
DAA5 : CedarR312,CedarR416
MAAZ : CedarR312,CedarR416
OAAR : CedarR312,CedarR416
bAAJ : CedarR312,CedarR416
UAAZ : CedarR312,CedarR416
9AAN : CedarR312,CedarR416
pAAL : CedarR312,CedarR416
AAAX : CedarR312,CedarR416
1AAN : CedarR312,CedarR416

JOINT RESOURCES:

UPPER BOUNDS:
xAAP < 3
bAAN < 101
DAA5 < 6
MAAZ < 9
OAAR < 12
bAAJ < 20
UAAZ < 26
9AAN < 47
pAAL < 81
AAAX < 81
1AAN < 101

TIGHT LOWER BOUNDS:
xAAP >= 0
bAAN >= 94
DAA5 >= 1
MAAZ >= 4
OAAR >= 9
bAAJ >= 16
UAAZ >= 24
9AAN >= 43
pAAL >= 78
AAAX >= 78
1AAN >= 97

###############################################
Welcome to the CBC MILP Solver
Version: 2.9.0
Build Date: Feb 12 2015

command line - /usr/local/lib/python2.7/site-packages/pulp/solverdir/cbc/osx/64/cbc /var/folders/_1/tvzc9mcd04lglh37bdvz4j_40000gn/T/8357259efff9423dab98b44db852fdf5-pulp.mps ratio 0.0 branch printingOptions all solution /var/folders/_1/tvzc9mcd04lglh37bdvz4j_40000gn/T/8357259efff9423dab98b44db852fdf5-pulp.sol (default strategy 1)
At line 2 NAME MODEL
At line 3 ROWS
At line 1947 COLUMNS
At line 259609 RHS
At line 261552 BOUNDS
At line 263353 ENDATA
Problem MODEL has 1942 rows, 1800 columns and 253911 elements
Coin0008I MODEL read with 0 errors
ratioGap was changed from 0 to 0
Problem is infeasible - 0.10 seconds
Option for printingOptions changed from normal to all
Total time (CPU seconds): 0.19 (Wallclock seconds): 0.23

INFO: execution time for solving mip (sec) = 1.54572701454
ERROR: no solution found
[]
`

And I've worked it out by hand, and there definitely is a solution for this:

img_0405

Any thoughts as to why it's not finding a solution? I'd really appreciate any direction with this.

[Feature request] Addition of CAP constraints

Adding CAP constraints seems to be the only way to achieve constraints on consecutive tasks where order matter.

Example: (From A Flexible Mixed Integer Programming framework for Nurse Scheduling C6 constraint)

Consecutive shifts Night-Morning are not permitted in the schedule.

x[n...1,d+1] + x[n...3,d] ≤ 1 ∀ n, d, ...s1, ..., sn

Where s = 3 represents night shift and s = 1 represents morning shift

Note: Night -> morning is forbidden, but morning -> night is allowed.

Naive approach that does not work:

for n in nurses:
    for d in range(horizon-1):
        S += (n[d]['night'] + n[d+1]['morning']) <= 1

Give the following error:

# => TypeError: unsupported operand type(s) for +: '_Capacity' and '_Capacity'

Sorting Jobs

Hi Tim,

Can i ask a question about dispatching jobs? I created process route, production resources etc. But when
code is scheduling, which parameters is minimizing? How can we manage scheduling parameters?

Thank you.

schedule for telescope time and tricky constraints

I am using the library to schedule observing time at the telescope site. The setting/problem of the observation schedule goes like this:

Resource:
X hours of telescope time
telescope = S.Resource('telescope')

Tasks:
celestrial objects (e.g. star A, star B) that need to be observed of Y hours each.
starA = S.Task('StarA', Y)
starB = S.Task('StarB', Y)

Constraints:
Each celestial target are only available at a certain time of the day. E.g. starA 17:00-20:00, starB 00:00-04:00. And the telescope can point to a star at a time. So I convert this into having the tasks, i.e. starA, starB, to have the condition:
S += starA + 7 << starB
S += starB + 11 < starB

In addition, there are 'recharge' tasks to do for the telescope. The telescope need to be 'recharged' for Z hours and then can run for the next R hours. Let's say we know we want to observe for 2 days and need 2 recharge, i.e.
recharge1 = S.Task('Recharge1', Z)
recharge2 = S.Task('Recharge2', Z)

Now my question is how to setup constraints so it can assign the two recharge tasks properly such that they are done within the R hours from the previous recharge tasks. The tricky part is the current <=, <, or << constraints don't do this requirement properly, as these operators seem to give hard bound between tasks, instead of a range.

Hope you can give some insight.

Precedence & Periods combined constraint

@timnon -- first off, this is an extremely useful package. Thank you for making it available.

I would like to schedule something like

Task A must occur before Task B
Task B must occur at times [t1,t2,t3,t4]

(The real use case is a bit more complex, but that's the gist of it)

Consider the following minimal example(in the style of test-solvers.py, and using its two_task_scenario()):

def PRECEDENCE_PERIODS():
    S = two_task_scenario()
    S['T1'] += S['R1']
    S['T2'] += S['R1']

    S +=S['T2'] > S['T1']
 # S['T2'].periods = [0,1,2,3,4,5,6,7,8,9,10]
    S['T2'].periods = [5,6,7,8,9,10]

    sols = ['[(T1, R1, 1, 2), (T2, R1, 3, 4)]']
    solvers.mip.solve(S, msg=1)
    print(S.solution())
    plotters.matplotlib.plot(S, fig_size=(10, 5), vertical_text=True)

    return S,sols

gets me :


  File "[REDACTED]Python/2.7/lib/python/site-packages/pyschedule/solvers/mip.py", line 216, in build_mip_from_scenario
    [(x[P.task_right, t_],-1) for t_ in range(t+P.task_left.length+P.offset,self.horizon)]
KeyError: (T2, 1)

Allowing any value for T2.periods, or removing the precedence constraint solves the problem .

This should be possible, correct? Or am I going about it wrong?

Brakes between tasks allowing long tasks.

Hi, I have a following problem: I would like to schedule some meetings (tasks) and allow brakes between them when cumulatively they take too long (more than 4 time slots). In the same time I would like to allow tasks taking more than the maximum 4 time slots. Let's say that I have a 1 person and 3 meetings:

person = scenario.Resource('person')
meeting1 = scenario.Task('meeting1', 1)
meeting2 = scenario.Task('meeting2', 2)
meeting3 = scenario.Task('meeting3', 5)

Then the desired solution would be for example [meeting1, meeting2, break, meeting3].
I've tried to make a restriction:

MAX_CONSECUTIVE_SLOTS = 4
for slot in range(HORIZON):
    scenario += person[slot:slot + MAX_CONSECUTIVE_SLOTS + 1] <= MAX_CONSECUTIVE_SLOTS

but this works only when all meetings are no longer than MAX_CONSECUTIVE_SLOTS. I've also try to combine this condition with the number of tasks per time slice:

meeting1.count = 1
meeting2.count = 1
meeting3.count = 1

for slot in range(HORIZON):
    scenario += (person[slot:slot + MAX_CONSECUTIVE_SLOTS + 1] <= MAX_CONSECUTIVE_SLOTS) or \
        (person['count'][slot:slot + MAX_CONSECUTIVE_SLOTS + 1] <= 1)

But person['count'][n:m] apparently means the number of tasks finished in given time slice, when I need the number of tasks overlapping this slice.

Any help would be much appreciated.

Floor Job shop scheduling Problem use two resource for single task with priority and setup time constrains

Hii ...
I am having problem while scheduling jobs like...
having one task T1 which can be perform on resource R1|R2 but resource R1 take less time than R2 due to setup time for R2 is more ..
So is there any way, i can add setup time and priority for resources R1|R2 for task T1..
Like
T1 = R1|R2
R1 have high priority than R2
I can added constrain of setup time for task T1 for Resource R1 and R2 ..

Link resources

Hi,

I'm currently investigating if pyschedule can help me generate a planning for machines and tools. For optimal result the tools and machines should be linked for as long as possible but because of more tools than machines and timing of orders tool changes are needed.

I didn't find an direct way to make such a link. I tried several tings with idle time tasks for the tools with a connected change task for tool and machine. But had the idea that the required option didn't functioned.

I hope that you can tell me if what i did makes sense and if there is an option I missed.

Thanks in advance,

from pyschedule import Scenario, solvers, plotters
import random

class Order:
    def __init__(self, delTime, prodTime, tool):
        #time in days
        self.delTime = delTime
        self.prodTime = prodTime
        self.tool = tool 

def genOrders(nOrders, meanDelTime, meanProdTime):
    orders = []
    for i in range(nOrders):
        orders.append(Order(int(random.normalvariate(meanDelTime,5)),int(random.normalvariate(meanProdTime,1)),random.randint(0,4)))
    return orders
        
scen = Scenario('factory_simple', horizon = 25)

machs = [scen.Resource('Mach_%d' % i) for i in range(3)]
tools = []
tasks = []

for i in range(5):
    tools.append(scen.Resource('Tool_%d"' % i, size=2))

    #tool change
    tasks.append(scen.Task('TC_%d' % i))
    tasks[-1].required= False
    tasks[-1] += [machs[0]|machs[1]|machs[2],tools[-1]]
    #tool idle
    tasks.append(scen.Task('Idle_%d' % i,length = 2))
    tasks[-1] += tools[-1]
    tasks[-1].required= False
    #after idle time tool change
    scen += tasks[-1]  <= tasks[-2]


orders = genOrders(10,25,3)
for i, order in enumerate(orders):      
    tasks.append(scen.Task('Order_%d'%i,length = order.prodTime))
    scen += tasks[-1] < order.delTime      
    tasks[-1] += [machs[0]|machs[1]|machs[2],tools[order.tool]]
                 
         
# Solve and print solution
scen.use_makespan_objective()
#scen.use_flowtime_objective()
solvers.mip.solve(scen,msg=1)

# Print the solution
print(scen.solution())    

plotters.matplotlib.plot(scen,fig_size=(15,10))

SCIP doesnt use objective

The current SCIP integration doesnt seem to support an objective from pulp. E.g. in the following example the completion time cost of task T2 is ignored:

#! /usr/bin/python
import sys
sys.path.append('../src')
import pyschedule

# get input size
S = pyschedule.Scenario('test',horizon=10)

R = S.Resource('R')
T1 = S.Task('T1')
T2 = S.Task('T2',completion_time_cost=1)
T1 += R
T2 += R

if pyschedule.solvers.mip.solve(S,msg=1,kind='SCIP'):
	pyschedule.plotters.matplotlib.plot(S,color_prec_groups=False)
else:
	print('no solution found')

Sequence of Tasks Must be Completed by Same Resource

Great work so far on pyschedule!

I have a question on how to do something, or possibly a feature request.

How can I define that a series of tasks must be completed by the same resource?

For example a truck delivering goods can make a round trip from the depot to the customer and back again. While one truck is unloading at the customer, a second truck could be driving to the customer. But I don't want to schedule for the 2nd truck to complete the unloading of the 1st truck.

S = pyschedule.Scenario('delivery')

# Two trucks
Truck_A = S.Resource('A')
Truck_B = S.Resource('B')

# Two deliveries
a_deliver, a_unload, a_return = S.Task('a_deliver', 30), S.Task('a_unload', 15), S.Task('a_return', 30)
b_deliver, b_unload, b_return = S.Task('b_deliver', 30), S.Task('b_unload', 15), S.Task('b_return', 30)

# Task sequences
S += a_deliver < a_unload, a_unload < a_return
S += b_deliver < b_unload, b_unload < b_return

# Customer
customer = S.Resource('customer')

# Only one resource (dock) for customer
a_unload += customer
b_unload += customer

# Task can be assigned to either truck
a_deliver += Truck_A | Truck_B
a_unload += Truck_A | Truck_B
a_return += Truck_A | Truck_B
b_deliver += Truck_A | Truck_B
b_unload += Truck_A | Truck_B
b_return += Truck_A | Truck_B

pyschedule.solvers.pulp.solve(S)

Which returned this solution:


##############################################

SCENARIO: delivery

OBJECTIVE: 

RESOURCES:
A
B
customer

TASKS:
a_deliver at 45 on [A] : [A | B]
a_unload at 75 on [customer, B] : [customer, A | B]
a_return at 90 on [B] : [A | B]
b_deliver at 0 on [A] : [A | B]
b_unload at 30 on [customer, B] : [customer, A | B]
b_return at 45 on [B] : [A | B]

PRECEDENCES:
a_deliver < a_unload
a_unload < a_return
b_deliver < b_unload
b_unload < b_return

##############################################

As you can see, because I do not know how to set a constraint with one resource per task group, it scheduled Truck_A to deliver both deliveries, but Truck_B to unload and return.

PERIODS - bug with non-consecutive slots

I was hoping to use periods for setting "offshift" calendar patterns.

resource1, resource2 = S.Resource('R1'), S.Resource('R2',periods=[1,4,5])

task1 = S.Task('task1',3)
task2 = S.Task('task2',5)

task1 += resource1
task2 += resource2

INFO: execution time for solving mip (sec) = 0.02400493621826172
INFO: objective = 1.0
[(task1, R1, 0, 3), (task2, R2, 1, 6)]

I was expecting either task2 to fill available periods, until time criteria has been met or to start at a point where there is enough available slots in a row to schedule the task.

In this case and others I have tested the task starts at the first available period (1) and still continues on 2 and 3 ignoring that fact they are not available.

Is this the expected outcome?

Thanks

Enable two tasks to use the same resource but only if the tasks have something in common

Hello,

First of all I'd like to say you made some amazing piece of software ! I really love it.

I'm using it to create schedules for my school. I was able to successfully create a schedule and everything, but I wonder about something I would really need.

Our school has grades (1, 2, 3, 4, 5, 6), separated into classes (1 Group a, 1Gb, 1Gc, 1Gd, 2Ga, 2Gb, ...). Each class is a resource, and each of the teachers is also a resource. Each exam is a task.

Tasks are like so: each task has a teacher resource, and a resource for every class that has that exam. Here is an example rendered schedule (the first four lines are what I am looking for):

figure_1

I have one teacher, here named Derenne_C., that gives "5_sc_general" and "5_a_bio". The thing is, as all students in the grade 5 take exams in a common place, he can give the exams at the same time (in the eyes of the program, he would be at two places at the same time, so he cannot). Across grades though, this is not possible (he can give to exams for grade 5 at the same time, but he cannot give an exam for grade 4 and an exam for grade 5 at the same time).

Here's a gist of my code. I store teachers data, class data, etc. in a config.yml file in order for it not to look like this.

Is there a way of doing this ?

Thank you !

Related tasks on different resources

Hello,

Is it possible to make the tasks related by precedence, to work on different resources? Something like that from your example, but vice versa

S += green_paint < green_post
# green_post will use the same resources as green_paint if there is an overlap in resource requirement
green_post += green_paint*[Alice,Bob]

Thnx!

Distributing tasks and giving tasks different "value"

I've been playing around with pyschedule and was wondering if it was possible to get the either or both of the following behaviors given the libraries current features:

  • Distribute tasks more evenly between resources. Say I have 3 resources each with a capacity of 3, and then I schedule 3 tasks that all occur at the same time. Given the following code, I end up with one of the 3 resources assigned with all 3 tasks, and the other two get no work. Is there a way to make it so that each resource is assigned 1 of the tasks instead?
from pyschedule import Scenario, solvers, plotters
import pyschedule

def test_setup():
    task_dt = 2
    task_len = 2*task_dt
    task_time = 10

    S = Scenario('test', horizon=20)
    resources = {name: S.Resource(name, size=3) for name in ['A', 'B', 'C']}
    tasks = {}
    for k in xrange(3):
        tasks[str(k)] = S.Task('task_{}'.format(k), task_len)
        T = tasks[str(k)]
        S += T >= task_time - task_dt
        S += T < task_time + task_dt

    for k in tasks:
        tasks[k] += pyschedule.alt(S.resources())
    return S

S = test_setup()
# Solve and plot scenario
if solvers.mip.solve(S, kind='CBC', msg=1, random_seed=6):
    %matplotlib inline
    plotters.matplotlib.plot(S, fig_size=(12,5))
else:
    print('no solution found')
  • Given tasks that are scheduled at a fixed time (as in the above example), and there are not enough resources available at certain times to process all of the tasks that need to be completed, I'd like to assign each task a value, make each optional and them schedule them such that the sum of the values of the tasks that are assigned is maximized (or conversely the total value of the skipped tasks at a certain time is minimized). Is there a way to do this?

Thanks in advance for any insight anyone can provide.

Setup time

How can I add setup time to jobs when using flowshop? It is very usual to setup a machine before put an another kind of job.

Sometimes accordingly setup times of each job it can be a bad ideia to schedule these jobs in a specific order because that order will have a extra coust of time in reason of setup time between jobs of different kind.

Very slow...

Hi, Timnon

At first, there is a miss.
In pyschedule.py, "apply" is used, and could not run with python 3+.

Another issue seems very fatal: it's very very slow... too slow to use.
I'm not familiar with PuLP, perhaps that's because PuLP. Or it's normal, that scale's problem should spend that time...
What about your opinion? Is there anything could be improved?
Thanks!

I just tested task and resource, even without any constraints.

from pyschedule import Scenario, solvers, alt
import random

def PerformanceTest(rescnt, taskcnt, sharedcnt):
    '''
        Resource count, Task count, Max Shared Resource count
    '''
    S = Scenario('hello_pyschedule',horizon=10000)

    for i in range(0, rescnt):
        S.Resource('R' + str(i))

    for i in range(0, taskcnt):
        S.Task('T' + str(i), len=random.randint(1,10))
        S['T' + str(i)] += alt(S['R' + str(x)] for x in random.sample(range(rescnt), random.randint(1,sharedcnt)))

    S.use_makespan_objective()
    #S.use_flowtime_objective()
    solvers.mip.solve_bigm(S,msg=1)

PerformanceTest(10, 20, 2)

Is this kind of problem solvable?

Hello - forgive the newbie question but, I wonder, is it possible to use your module for this kind of problem? :

I have:

  • list of patients at hospitals
  • list of roaming doctors
  • list of possible dates next month, to work
  • list of constraints, such as "patient 10 is not available mondays", "doctor 3 is not available 4 May 2017", "a doctor can see only 15 patients per day", "a doctor can work only 2 days per week"

I would like to solve this and get a list of suggested dates to schedule, that don't have any conflicts.

Just trying to wrap my head around how this would be done using your module @timnon. Appreciate any guidance you can provide.

Sincerely,
Rick

Single task to multiple Jobs

Hi am using Pyschedule to try and optimize my complex ETL process. I extract multiple tables from an ERP system and create about 50 different models. Each model can only start once all the required tables for that model is extracted. Most tables goes to multiple models and some extracts run for quite a long time.

Any help would be highly appreciated.

TIGHTPLUS constraints: how to set a difference between 2 tasks without knowing their order

Hi,
I have to set up some TIGHTPLUS constraints in order to separate my tasks by a few time units: but how to set a difference between 2 tasks without knowing their order ?
For example, I'm using such TIGHTPLUS ... S += Games[11] + 1 <= Games[10],
but I don't want to set an order. I want tasks Games[11] and Games[10] to be separated by 1 time unit whatever the order they will be appearing ...

Sports schedule: home/away alternation

Hi again,
I've been spending my days trying to create a sports schedule with pyschedule, but I cannot implement a home/away alternation constraint. Each matches calendar should get the teams to alternate home and away matches in order not to play e.g. 5 times at home or away in a row.
I know a capacity constraint should do the job, but I don't see how.
Any thoughts about this ?
I would really appreciate any tips or advice.

Specify resource start time

Hello sir,
First, I want to thank you for this outstanding package.
How can I specify a start time for a resource ? the objective is to oblige all resources to start at time = 0.
Thank you in advance !

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.