timnon / pyschedule

pyschedule - resource scheduling in python
Apache License 2.0
295 stars 61 forks source link

Brakes between tasks allowing long tasks. #69

Closed martaczc closed 6 years ago

martaczc commented 6 years ago

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.

timnon commented 6 years ago

The easiest way to implement breaks after a certain number of tasks is to introduce some custom parameter, e.g. you can call it stress, and then define how much a meeting increases the stress and a break reduces the stress. Finally, put some bounds on the stress levels:

from pyschedule import Scenario, solvers, plotters

horizon = 20

S = Scenario('test',horizon=horizon)
meetings = S.Tasks('meeting',num=10,stress=1)
breaks = S.Tasks('breaks',schedule_cost=0,num=10,stress=-3)

person = S.Resource('person')
meetings += person
breaks += person

for t in range(horizon):
    S += person['stress'][:t] <= 3
    S += person['stress'][:t] >= 0

if solvers.mip.solve(S,msg=0,kind='CBC'):
    print(S.solution())
else:
    print('no solution found')
martaczc commented 6 years ago

Thank you for your response! Base on it I've solved my problem. I've just needed to set stress equal to task length for short meetings and stress equal to stress_limit for longer tasks. And iterate from 0 to (horizon + 1), not to horizon when adding restrictions. Otherwise the most stressing event is set at the time horizon, because the last time slot has no stress restriction.

from pyschedule import Scenario, solvers

horizon = 20
stress_limit = 4

S = Scenario('test', horizon=horizon)

meeting1 = S.Task('meeting1', 1, stress=1)
meeting2 = S.Task('meeting2', 2, stress=2)
meeting3 = S.Task('meeting3', 5, stress=stress_limit)
breaks = S.Tasks('break', schedule_cost=0, num=3, stress=-stress_limit)

person = S.Resource('person')
meeting1 += person
meeting2 += person
meeting3 += person
breaks += person

for t in range(horizon + 1):
    S += person['stress'][:t] <= stress_limit
    S += person['stress'][:t] >= 0

S.clear_solution()
S.use_flowtime_objective()

if solvers.mip.solve(S, msg=0, kind='CBC'):
    print(S.solution())
else:
    print('no solution found')