tpaviot/ProcessScheduler

ResourceUnavailable constraint specification

Closed this issue · 3 comments

rnwolf commented

I seem to have a problem specifying when a specific resource is unavailable. I am looking to crate a model in which specified resources are not working weekends for example.

When I specify that Albert is unavailable with:

ps.ResourceUnavailable(Albert, [(1, 4), (6, 9)]) # Try a specific named worker

I still have Albert scheduled to work on tasked in those time periods.

Any suggestions?

albert-unavilable

#!/usr/bin/env python
# coding: utf-8

import processscheduler as ps
from datetime import timedelta, datetime

# Create the scheduling problem
# The total horizon is not known

agile_problem = ps.SchedulingProblem('agile-priorities')
# ,delta_time=timedelta(days=1))

# nb_fullstack = 1
# nb_backend = 1
# nb_frontend = 1
# nb_testers = 2
#
# Team with skills
# fullstack = [ps.Worker('Full%i' % (i + 1)) for i in range(nb_fullstack)]
# backend = [ps.Worker('Back%i' % (i + 1)) for i in range(nb_backend)]
# frontend = [ps.Worker('Front%i' % (i + 1)) for i in range(nb_frontend)]
# tester = [ps.Worker('Tester%i' % (i + 1)) for i in range(nb_testers)]

# Alternative try with named workers

Albert = ps.Worker('Albert')  # Fullstack
Bob = ps.Worker('Bob')  # Backend
Fred = ps.Worker('Fred')  # Frontend
Ted = ps.Worker('Ted')  # Tester
Tony = ps.Worker('Tony')  # Tester

ps.ResourceUnavailable(Albert, [(1, 4), (6, 9)])  # Try a specific named worker

# Create tasks and assign resources
# One period is mapped to one day.

kick_off_epic_1 = ps.FixedDurationTask('KickOffEpic_1', duration=1)
kick_off_epic_1.add_required_resource(ps.SelectWorkers([Albert, Bob, Fred, Ted]))

work_item_1 = ps.FixedDurationTask('WorkItem_1', duration=3)
work_item_1.add_required_resource(Albert)

work_item_2 = ps.FixedDurationTask('WorkItem_2', duration=2)
work_item_2.add_required_resource(Bob)

work_item_3 = ps.FixedDurationTask('WorkItem_3', duration=2)

# the testing activity can be processed by the Tester1 OR Tester2
work_item_3.add_required_resource(ps.SelectWorkers([Ted, Tony],
                                                   nb_workers_to_select=1,
                                                   kind='exact'))

work_item_4 = ps.FixedDurationTask('WorkItem_4', duration=2)
work_item_4.add_required_resource(Albert)


release_epic_1 = ps.FixedDurationTask('ReleaseEpic_1', duration=1)
release_epic_1.add_required_resource(Tony)

ps.TaskStartAt(kick_off_epic_1, 0)
ps.TaskEndAt(release_epic_1, agile_problem.horizon)

# Task precedences

ps.TaskPrecedence(kick_off_epic_1, work_item_1)
ps.TaskPrecedence(work_item_1, work_item_2)
ps.TaskPrecedence(work_item_2, work_item_3)
ps.TaskPrecedence(work_item_3,work_item_4)
ps.TaskPrecedence(work_item_4,release_epic_1)

# First solution, plot the schedule

solver = ps.SchedulingSolver(agile_problem)
solution_1 = solver.solve()
solution_1.render_gantt_matplotlib(fig_size=(10, 5), render_mode='Resource')


Hey @rnwolf!
I ran into the same issue and found out what was going on when I exchanged the line

ps.ResourceUnavailable(Albert, [(1, 4), (6, 9)])  # Try a specific named worker

with

ps.WorkLoad(Albert, {(1, 4) : 0, (6, 9): 0})  # Try a specific named worker

since the docs mention that ps.ResourceUnavailable is a special case of ps.WorkLoad.

Now I got the following error:

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[128], line 29
     26 Ted = ps.Worker('Ted')  # Tester
     27 Tony = ps.Worker('Tony')  # Tester
---> 29 ps.WorkLoad(Albert, {(1, 4) : 0, (6, 9): 0})  # Try a specific named worker
     31 # Create tasks and assign resources
     32 # One period is mapped to one day.
     34 kick_off_epic_1 = ps.FixedDurationTask('KickOffEpic_1', duration=1)

File ~/.mambaforge/envs/pm/lib/python3.11/site-packages/processscheduler/resource_constraint.py:124, in WorkLoad.__init__(self, resource, dict_time_intervals_and_bound, kind, optional)
    121         durations.append(dur)
    123 if not durations:
--> 124     raise AssertionError(
    125         "The resource is not assigned to any task. WorkLoad constraint meaningless."
    126     )
    128 # workload constraint depends on the kind
    129 if kind == "exact":

AssertionError: The resource is not assigned to any task. WorkLoad constraint meaningless.

So I moved the line which adds the resource constraint after the assignment of the resource (Albert) to tasks:

 ...
 # Alternative try with named workers

 Albert = ps.Worker('Albert')  # Fullstack
 Bob = ps.Worker('Bob')  # Backend
 Fred = ps.Worker('Fred')  # Frontend
 Ted = ps.Worker('Ted')  # Tester
 Tony = ps.Worker('Tony')  # Tester

-# moved resource constraint from here...

-ps.ResourceUnavailable(Albert, [(1, 4), (6, 9)])  # Try a specific named worker

 # Create tasks and assign resources
 # One period is mapped to one day.

 kick_off_epic_1 = ps.FixedDurationTask('KickOffEpic_1', duration=1)
 kick_off_epic_1.add_required_resource(ps.SelectWorkers([Albert, Bob, Fred, Ted]))

 work_item_1 = ps.FixedDurationTask('WorkItem_1', duration=3)
 work_item_1.add_required_resource(Albert)

 work_item_2 = ps.FixedDurationTask('WorkItem_2', duration=2)
 work_item_2.add_required_resource(Bob)

 work_item_3 = ps.FixedDurationTask('WorkItem_3', duration=2)

 # the testing activity can be processed by the Tester1 OR Tester2
 work_item_3.add_required_resource(ps.SelectWorkers([Ted, Tony],
                                                    nb_workers_to_select=1,
                                                    kind='exact'))

 work_item_4 = ps.FixedDurationTask('WorkItem_4', duration=2)
 work_item_4.add_required_resource(Albert)


 release_epic_1 = ps.FixedDurationTask('ReleaseEpic_1', duration=1)
 release_epic_1.add_required_resource(Tony)

+# ... to here.
+
+# Add resource constraints after resources have been assigned to tasks
+ps.ResourceUnavailable(Albert, [(1, 4), (6, 9)])  # Try a specific named worker
+
 ps.TaskStartAt(kick_off_epic_1, 0)
 ps.TaskEndAt(release_epic_1, agile_problem.horizon)
 ...
Fully working example
import processscheduler as ps
from datetime import timedelta, datetime

# Create the scheduling problem
# The total horizon is not known

agile_problem = ps.SchedulingProblem('agile-priorities')
# ,delta_time=timedelta(days=1))

# nb_fullstack = 1
# nb_backend = 1
# nb_frontend = 1
# nb_testers = 2
#
# Team with skills
# fullstack = [ps.Worker('Full%i' % (i + 1)) for i in range(nb_fullstack)]
# backend = [ps.Worker('Back%i' % (i + 1)) for i in range(nb_backend)]
# frontend = [ps.Worker('Front%i' % (i + 1)) for i in range(nb_frontend)]
# tester = [ps.Worker('Tester%i' % (i + 1)) for i in range(nb_testers)]

# Alternative try with named workers

Albert = ps.Worker('Albert')  # Fullstack
Bob = ps.Worker('Bob')  # Backend
Fred = ps.Worker('Fred')  # Frontend
Ted = ps.Worker('Ted')  # Tester
Tony = ps.Worker('Tony')  # Tester

# Create tasks and assign resources
# One period is mapped to one day.

kick_off_epic_1 = ps.FixedDurationTask('KickOffEpic_1', duration=1)
kick_off_epic_1.add_required_resource(ps.SelectWorkers([Albert, Bob, Fred, Ted]))

work_item_1 = ps.FixedDurationTask('WorkItem_1', duration=3)
work_item_1.add_required_resource(Albert)

work_item_2 = ps.FixedDurationTask('WorkItem_2', duration=2)
work_item_2.add_required_resource(Bob)

work_item_3 = ps.FixedDurationTask('WorkItem_3', duration=2)

# the testing activity can be processed by the Tester1 OR Tester2
work_item_3.add_required_resource(ps.SelectWorkers([Ted, Tony],
                                                   nb_workers_to_select=1,
                                                   kind='exact'))

work_item_4 = ps.FixedDurationTask('WorkItem_4', duration=2)
work_item_4.add_required_resource(Albert)


release_epic_1 = ps.FixedDurationTask('ReleaseEpic_1', duration=1)
release_epic_1.add_required_resource(Tony)

# Add resource constraints after resources have been assigned to tasks
ps.ResourceUnavailable(Albert, [(1, 4), (6, 9)])  # Try a specific named worker

ps.TaskStartAt(kick_off_epic_1, 0)
ps.TaskEndAt(release_epic_1, agile_problem.horizon)

# Task precedences

ps.TaskPrecedence(kick_off_epic_1, work_item_1)
ps.TaskPrecedence(work_item_1, work_item_2)
ps.TaskPrecedence(work_item_2, work_item_3)
ps.TaskPrecedence(work_item_3,work_item_4)
ps.TaskPrecedence(work_item_4,release_epic_1)

# First solution, plot the schedule

solver = ps.SchedulingSolver(agile_problem)
solution_1 = solver.solve()
solution_1.render_gantt_matplotlib(fig_size=(10, 5), render_mode='Resource')

Rendered Gantt chart:
agile-priorities-fixed

So, the issue here was that there was no indication to the user that the order of task assignment and adding resource constraints matter in the case of ps.ResourceUnavailable.

@tpaviot Thanks for this amazing package!

I had already a very quick look at the class definition, but was not a able to directly see how to add an assertion there.

Could you please have a look?

Related issue: #110

Brilliant I will experiment some more.

Thank you guys for your feedback.