tpaviot / ProcessScheduler

A Python package for automatic and optimized resource scheduling
https://processscheduler.github.io/
GNU General Public License v3.0
58 stars 17 forks source link

Breaking a task over availability list - is this not a function? #117

Open asoral opened 1 year ago

asoral commented 1 year ago

I am thankful for all your efforts on processscheduler github repo. I have used in my project but I am stuck at a point and can't seem to move past this problem. I have tried several things but can't seem to absorb the fact that such an excellent library can't tackle my basic issue.

I have a project with more than 150 tasks. Each task can only be assigned to one worker/resource at a time. All my tasks are of some duration, on this example consider task-T for fixed duration 14hours (Horizon is hours)

Now, resources are only available during a certain shift timings. In this example consider one resource with a shift of 8AM-5PM daily.

I have converted unavailable hours into list of unavailable horizons. In this example (0,8) (17,24) for first day, (24,32) (41,48) for day 2 and so on for all my planning horizon days.

Problem: solver is not able to break my task into smaller portions because my resource or worker is only available for 9 hours in a single go. Because of which the first task is planned from the day resource constrains are not defined. Leaving all my worked useless for that duration.

I even tried variable duration task but that also didn't work as I expected.

tpaviot commented 1 year ago

Splitting a task may require an additional constraint, this is not hard coded, indeed a task is basically a time interval i.e. a lower bound and an upper bound. It should be possible to represent such a task using contiguous built-in classes, anyway more context is required. A code snippet would be welcome

asoral commented 1 year ago

Splitting a task may require an additional constraint, this is not hard coded, indeed a task is basically a time interval i.e. a lower bound and an upper bound. It should be possible to represent such a task using contiguous built-in classes, anyway more context is required. A code snippet would be welcome

->Task1 => 2 hours (fixed Duration Task); Constraint: TaskPrecedence Task2 ->Task2 => 11 hours (fixed Duration Task); Constraint: StartAfterStrict 14-03-2023 (DD-MM-YYYY) ->Task3 => Zero Duration Task; Constraint: TaskEndSynced Task1 ->Task4 => 20 hours (fixed Duration Task); Constraint: TaskPrecedence Task2

Unavailable Slots: [(0, 10), (19, 24), (24, 34), (43, 48), (48, 58), (67, 72), (72, 82), (91, 96), (96, 106), (115, 120), (120, 130), (139, 144), (144, 154), (163, 168), (168, 178), (187, 192), (192, 202), (211, 216), (216, 226), (235, 240), (240, 250), (259, 264), (264, 274), (283, 288)]

OUTPUT: { "buffers": {}, "horizon": 271, "indicators": { "Utilization (HR-EMP-00001)": 0, "Utilization (HR-EMP-00002)": 11 }, "problem_properties": { "problem_end_time": "2023-03-25 07:00:00", "problem_start_time": "2023-03-14 00:00:00", "problem_timedelta": "1:00:00" }, "resources": { "HR-EMP-00001": { "assignments": [ [ "TASK-2023-00001", 269, 271 ] ], "name": "HR-EMP-00001", "type": "Worker" }, "HR-EMP-00002": { "assignments": [ [ "TASK-2023-00005", 251, 271 ], [ "TASK-2023-00002", 240, 251 ], [ "TASK-2023-00004", 271, 271 ] ], "name": "HR-EMP-00002", "type": "Worker" } }, "tasks": { "TASK-2023-00001": { "assigned_resources": [ "HR-EMP-00001" ], "duration": 2, "duration_time": "2:00:00", "end": 271, "end_time": "2023-03-25 07:00:00", "name": "TASK-2023-00001", "optional": 0, "scheduled": true, "start": 269, "start_time": "2023-03-25 05:00:00", "type": "FixedDurationTask" }, "TASK-2023-00002": { "assigned_resources": [ "HR-EMP-00002" ], "duration": 11, "duration_time": "11:00:00", "end": 251, "end_time": "2023-03-24 11:00:00", "name": "TASK-2023-00002", "optional": 0, "scheduled": true, "start": 240, "start_time": "2023-03-24 00:00:00", "type": "FixedDurationTask" }, "TASK-2023-00004": { "assigned_resources": [ "HR-EMP-00002" ], "duration": 0, "duration_time": "0:00:00", "end": 271, "end_time": "2023-03-25 07:00:00", "name": "TASK-2023-00004", "optional": 0, "scheduled": true, "start": 271, "start_time": "2023-03-25 07:00:00", "type": "ZeroDurationTask" }, "TASK-2023-00005": { "assigned_resources": [ "HR-EMP-00002" ], "duration": 20, "duration_time": "20:00:00", "end": 271, "end_time": "2023-03-25 07:00:00", "name": "TASK-2023-00005", "optional": 0, "scheduled": true, "start": 251, "start_time": "2023-03-24 11:00:00", "type": "FixedDurationTask" } } }

EXPECTED OUTPUT:

tpaviot commented 1 year ago

A Gantt diagram would certainly be better to present the result. I don't understand the 1000, 1200 or 1900 numbers. What do they represent? what is the unit?

asoral commented 1 year ago
#problem definition
# First we created New problem
datetime_obj=datetime.now()
problem = ps.SchedulingProblem(str(project),delta_time=timedelta(hours=1),start_time=datetime_obj)

#plan objective
problem.add_objective_makespan(weight=1)

#Task define
#In our scenario we have 4 task 
#1.fixed Duration: 3 task is fixed duration
t1=ps.FixedDurationTask(“TASK-2023-00001”,duration=2,work_amount=2,priority=1,optional=0)
t2=ps.FixedDurationTask(“TASK-2023-00002”,duration=11,work_amount=11,priority=1,optional=0)
t3=ps.FixedDurationTask(“TASK-2023-00005”,duration=20,work_amount=20,priority=1,optional=0)

#2.Zero duration:1 task is zero duration
 t4=ps.ZeroDurationTask(“TASK-2023-00004”,optional=0)

# Task Constraints
# Task t1 have TaskPrecedence constraint
ps.TaskPrecedence(d.get(t2,t1,offset=0,kind=“strict”,optional=0)
# Task t2 have TaskStartAfterLax constraint:
ps.TaskStartAfterLax(d.get(str(t2,duration=24,optional=0)
# task t3 have TaskPrecedence
ps.TaskPrecedence(d.get(t2,t3,offset=0,kind=“strict”,optional=0)
# task t4 have TasksEndSynced
 ps.TasksEndSynced(d.get(t4,t2,optional=task1.optional)

#Resource define
emp1=ps.SelectWorkers(emp1,nb_workers_to_select=1,kind='exact')
emp2=ps.SelectWorkers(emp2,nb_workers_to_select=1,kind='exact')

#Resource Constarints:
interval=[(0, 10), (19, 24), (24, 34), (43, 48), (48, 58), (67, 72), (72, 82), (91, 96), (96, 106), (115, 120), (120, 130), (139, 144), (144, 154), (163, 168), (168, 178), (187, 192), (192, 202), (211, 216), (216, 226), (235, 240)]
ps.ResourceUnavailable(emp1, interval, optional=False)
ps.ResourceUnavailable(emp2, interval, optional=False)

#indiactor applied:
#resource utilization
problem.add_indicator_resource_utilization(emp1)
problem.add_indicator_resource_utilization(emp2)

Output:

{ "buffers": {}, "horizon": 271, "indicators": { "Utilization (HR-EMP-00001)": 0, "Utilization (HR-EMP-00002)": 11 }, "problem_properties": { "problem_end_time": "2023-03-28 07:00:00", "problem_start_time": "2023-03-17 00:00:00", "problem_timedelta": "1:00:00" }, "resources": { "HR-EMP-00001": { "assignments": [], "name": "HR-EMP-00001", "type": "Worker" }, "HR-EMP-00002": { "assignments": [ [ "TASK-2023-00005", 251, 271 ], [ "TASK-2023-00002", 240, 251 ], [ "TASK-2023-00004", 271, 271 ] ], "name": "HR-EMP-00002", "type": "Worker" } }, "tasks": { "TASK-2023-00001": { "assigned_resources": [], "duration": 2, "duration_time": "2:00:00", "end": 271, "end_time": "2023-03-28 07:00:00", "name": "TASK-2023-00001", "optional": 0, "scheduled": true, "start": 269, "start_time": "2023-03-28 05:00:00", "type": "FixedDurationTask" }, "TASK-2023-00002": { "assigned_resources": [ "HR-EMP-00002" ], "duration": 11, "duration_time": "11:00:00", "end": 251, "end_time": "2023-03-27 11:00:00", "name": "TASK-2023-00002", "optional": 0, "scheduled": true, "start": 240, "start_time": "2023-03-27 00:00:00", "type": "FixedDurationTask" }, "TASK-2023-00004": { "assigned_resources": [ "HR-EMP-00002" ], "duration": 0, "duration_time": "0:00:00", "end": 271, "end_time": "2023-03-28 07:00:00", "name": "TASK-2023-00004", "optional": 0, "scheduled": true, "start": 271, "start_time": "2023-03-28 07:00:00", "type": "ZeroDurationTask" }, "TASK-2023-00005": { "assigned_resources": [ "HR-EMP-00002" ], "duration": 20, "duration_time": "20:00:00", "end": 271, "end_time": "2023-03-28 07:00:00", "name": "TASK-2023-00005", "optional": 0, "scheduled": true, "start": 251, "start_time": "2023-03-27 11:00:00", "type": "FixedDurationTask" } } }

As per the definition, tak t2 should start at 24th interval (on horizon). So, the start date of the task should be 18-03-23 (dd-mm-yy) but the resource is only working between 10,19 (shift timing) and he has 9 intervals available for work.

Expected result: Engine splits the task and consumes 9 hours (intervals) from 10-19 and then the next 9 intervals between 34-43. "TASK-2023-00002": { "assigned_resources": [ "HR-EMP-00002" ], "duration": 9, "duration_time": “09:00:00", "end": 19, "end_time": "2023-03-18 19:00:00", "name": "TASK-2023-00002", "optional": 0, "scheduled": true, "start": 10, "start_time": "2023-03-18 10:00:00", "type": "FixedDurationTask" },

"TASK-2023-00002": { "assigned_resources": [ "HR-EMP-00002" ], "duration": 2, "duration_time": “02:00:00", "end": 36, "end_time": "2023-03-19 12:00:00", "name": "TASK-2023-00002", "optional": 0, "scheduled": true, "start": 34, "start_time": "2023-03-19 10:00:00", "type": "FixedDurationTask" },

Actual Result: Engine completely skipped all the intervals because the resource was not available for the entire duration of the task (but was available for 9 hours every day which when combined would have sufficed for task to be done over 2.6 days of 8 interval each or a total of 24 intervals)

newplot (1)

itopaloglu83 commented 1 year ago

Conceptually, having breaks in worker schedules is akin to having tasks that are variable in duration depending on how many shift breaks they happen to overlap.

If a worker is only available for 10 hours on a single day (8am to 5pm), a task that spans 12 hours in reality takes 12 working hours plus shift breaks in between. 8am to 5pm the first day and 8am to 10am the second. With the 15 hours of break time (from 5pm to 8am the next day), the total task span is 27 hours.

Another way to solve this issue is to remove remove all the breaks from the schedule and solve the scheduling as a single timeline and then use another function to convert it to the real time.

Of course the real life is a lot more complicated with shops having machines and workers with different shifts and needing to finish a job in second shift even though it was started by another resource in the first shift.

asoral commented 1 year ago

@tpaviot thanks much for your efforts and I am reviewing the PR and will update there

Thanks again