-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from rodrigo-arenas/develop
Develop
- Loading branch information
Showing
10 changed files
with
388 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ pytest-cov==2.11.1 | |
twine==3.3.0 | ||
numpy>=1.18.1 | ||
ortools>=7.8.7959 | ||
pandas>=1.0.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
""" | ||
Requirement: Find the number of workers needed to schedule per shift in a production plant for the next 2 days with the | ||
following conditions: | ||
* There is a number of required persons per hour and day given in the matrix "required_resources" | ||
* There are 4 available shifts called "Morning", "Afternoon", "Night", "Mixed"; their start and end hour is | ||
determined in the dictionary "shifts_coverage", 1 meaning the shift is active at that hour, 0 otherwise | ||
* The number of required workers per day and period (hour) is determined in the matrix "required_resources" | ||
* The maximum number of workers that can be shifted simultaneously at any hour is 25, due plat capacity restrictions | ||
* The maximum number of workers that can be shifted in a same shift, is 20 | ||
""" | ||
|
||
from pyworkforce.shifts import MinRequiredResources | ||
|
||
# Columns are an hour of the day, rows are the days | ||
required_resources = [ | ||
[9, 11, 17, 9, 7, 12, 5, 11, 8, 9, 18, 17, 8, 12, 16, 8, 7, 12, 11, 10, 13, 19, 16, 7], | ||
[13, 13, 12, 15, 18, 20, 13, 16, 17, 8, 13, 11, 6, 19, 11, 20, 19, 17, 10, 13, 14, 23, 16, 8] | ||
] | ||
|
||
# Each entry of a shift, is an hour of the day (24 columns) | ||
shifts_coverage = {"Morning": [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | ||
"Afternoon": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], | ||
"Night": [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1], | ||
"Mixed": [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]} | ||
|
||
|
||
# The cost of shifting a resource if each shift, if present, solver will minimize the total cost | ||
cost_dict = {"Morning": 8, "Afternoon": 8, "Night": 10, "Mixed": 7} | ||
|
||
scheduler = MinRequiredResources(num_days=2, | ||
periods=24, | ||
shifts_coverage=shifts_coverage, | ||
required_resources=required_resources, | ||
cost_dict=cost_dict, | ||
max_period_concurrency=25, | ||
max_shift_concurrency=25) | ||
|
||
solution = scheduler.solve() | ||
|
||
print(solution) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
from pyworkforce.shifts.shifts_selection import MinAbsDifference | ||
from pyworkforce.shifts.shifts_selection import MinAbsDifference, MinRequiredResources | ||
|
||
__all__ = ["MinAbsDifference"] | ||
__all__ = ["MinAbsDifference", "MinRequiredResources"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
from ortools.sat.python import cp_model | ||
from pyworkforce.shifts.utils import check_positive_integer, check_positive_float | ||
|
||
|
||
class BaseShiftScheduler: | ||
def __init__(self, num_days: int, | ||
periods: int, | ||
shifts_coverage: dict, | ||
required_resources: list, | ||
max_period_concurrency: int, | ||
max_shift_concurrency: int, | ||
max_search_time: float = 240.0, | ||
num_search_workers=4): | ||
|
||
""" | ||
Base class to solve the following schedule problem: | ||
Its required to find the optimal number of resources (agents, operators, doctors, etc) to allocate | ||
in a shift, based on a pre-defined requirement of number of resources per period of the day (periods of hours, | ||
half-hour, etc) | ||
:param num_days: Number of days needed to schedule | ||
:param periods: Number of working periods in a day | ||
:param shifts_coverage: dict with structure {"shift_name": "shift_array"} where "shift_array" is an array of size [periods] (p), 1 if shift covers period p, 0 otherwise | ||
:param max_period_concurrency: Maximum resources allowed to shift in any period and day | ||
:param required_resources: Array of size [days, periods] | ||
:param max_shift_concurrency: Number of maximum allowed resources in a same shift | ||
:param max_search_time: Maximum time in seconds to search for a solution | ||
:param num_search_workers: Number of workers to search a solution | ||
""" | ||
|
||
is_valid_num_days = check_positive_integer("num_days", num_days) | ||
is_valid_periods = check_positive_integer("periods", periods) | ||
is_valid_max_period_concurrency = check_positive_integer("max_period_concurrency", max_period_concurrency) | ||
is_valid_max_shift_concurrency = check_positive_integer("max_shift_concurrency", max_shift_concurrency) | ||
is_valid_max_search_time = check_positive_float("max_search_time", max_search_time) | ||
is_valid_num_search_workers = check_positive_integer("num_search_workers", num_search_workers) | ||
|
||
self.num_days = num_days | ||
self.shifts = list(shifts_coverage.keys()) | ||
self.num_shifts = len(self.shifts) | ||
self.num_periods = periods | ||
self.shifts_coverage_matrix = list(shifts_coverage.values()) | ||
self.max_shift_concurrency = max_shift_concurrency | ||
self.max_period_concurrency = max_period_concurrency | ||
self.required_resources = required_resources | ||
self.max_search_time = max_search_time | ||
self.num_search_workers = num_search_workers | ||
self.solver = cp_model.CpSolver() | ||
self.transposed_shifts_coverage = None | ||
self.status = None |
Oops, something went wrong.