This article has participated in the "Newcomer Creation Ceremony" event to start the road of gold creation together.
background
Last weekend, I met my girlfriend on the schedule. It took almost an hour. I thought about using the code to save her some time.
Question research
I checked the Internet and read several papers to understand the background.
Problems like scheduling are collectively known as the Nurse Rostering Problem (NRP) problem, which is an NP-hard problem in terms of complexity
P problems are problems that can be solved in polynomial time, while NP problems are problems that can be verified for correctness in polynomial time
What does polynomial time mean? I understand it is the algorithm complexity of the usual calculation
O(1) – constant-time
O(log_2(n)) – logarithmic-time
O(n) – linear-time
O(n^2) – quadratic-time
O(n^k) – polynomial-time
O(k^n) – exponential-time
O(n!) – factorial-time
复制代码
A description of the NP problem can be found in this article I found
The complexity theory is mentioned because the right tools can be found only after analyzing the problem more precisely.
We will think of using methods in computer science to deal with such problems, such as machine learning.
Tool Finder - Find the shoulders of giants
The technology stack of machine learning, what I have learned is scikit-learn, TensorFlow and Keras in Python to make choices
As far as I know, this is a topic that has been studied for many years. In this case, there should be ready-made trained models to do this kind of thing.
In the end, I found OR-Tools ( Official Site ) developed by Google. It already has models trained to handle this kind of scheduling problem.
Action
Code example
Official example Source Code
The main thing is to adjust the source code to meet the actual needs.
Basic usage of or-tools
Pseudocode excerpted to understand how it works
// 选择和定义一个model,然后设置所需的前置数据和限制条件
model = cp_model.CpModel()
model.NewBoolVar(...)
model.AddExactlyOne(...)
model.Add(...)
// 选择和定义一个solver,然后针对model进行optimizate
solver = cp_model.CpSolver()
status = solver.Solve(model, solution_printer)
// 创建一个矩阵进行拟合
work = {}
for e in range(num_employees):
for s in range(num_shifts):
for d in range(num_days):
work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d))
// 下面是打印结果的算法
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
print()
header = ' '
for w in range(num_weeks):
header += 'M T W T F S S '
print(header)
for e in range(num_employees):
schedule = ''
for d in range(num_days):
for s in range(num_shifts):
if solver.BooleanValue(work[e, s, d]): // NOTE: 获取优化算法出来的结果
schedule += shifts[s] + ' '
print('worker %i: %s' % (e, schedule))
print()
复制代码
Parameter adjustment
The parameter comments in the example source code clearly indicate the usage, and it is enough to make appropriate adjustments to the needs of practical problems. The following pseudo code is just for some description of the parameters.
num_employees = 4 // 员工数,改为目标的员工数
num_weeks = 5 // 工作天数,目标是排一个月的班表,改为5周
shifts = ['O', 'M', 'A', 'N'] // 班期,和目标一样
# Fixed assignment: (employee, shift, day).
# 固定前两天的,排班前得动态调整下
fixed_assignments = [
(0, 0, 0),
(1, 1, 0),
(2, 2, 0),
(3, 3, 0),
(0, 0, 1),
(1, 1, 1),
(2, 2, 1),
(3, 3, 1),
]
# Request: (employee, shift, day, weight)
# 员工想要固定休息的时间(位置),权重值我理解为代表拟合时可调整的优先级
requests = [
# Employee 3 does not want to work on the first Saturday (negative weight for the Off shift).
(3, 0, 5, -2),
# Employee 2 does not want a night shift on the first Friday (positive weight).
(2, 3, 4, 4)
]
# Shift constraints on continuous sequence :
# (shift, hard_min, soft_min, min_penalty,
# soft_max, hard_max, max_penalty)
# hard_min: 硬性限制,在周期内最少连续要上的班期天数
# soft_min: 软性限制,在周期内最少连续要上的班期天数
# soft_max和hard_max同上去理解
shift_constraints = [
# One or two consecutive days of rest, this is a hard constraint.
(0, 1, 1, 0, 2, 2, 0),
# betweem 2 and 3 consecutive days of night shifts, 1 and 4 are
# possible but penalized.
(3, 1, 2, 20, 3, 4, 5),
]
# Weekly sum constraints on shifts days:
# (shift, hard_min, soft_min, min_penalty,
# soft_max, hard_max, max_penalty)
weekly_sum_constraints = [
# 每周最少要休息的天数
(0, 1, 2, 7, 2, 3, 4),
]
# Penalized transitions:
# (previous_shift, next_shift, penalty (0 means forbidden))
penalized_transitions = [
# 尽量避免午班换晚班
(2, 3, 4),
# 晚班不能接着早班
(3, 1, 0),
]
# daily demands for work shifts (morning, afternon, night) for each day
# of the week starting on Monday.
# 每个班期最少要有多少人,我这里的现实问题是有一个人就行了
weekly_cover_demands = [
(1, 1, 1), # Monday
(1, 1, 1), # Tuesday
(1, 1, 1), # Wednesday
(1, 1, 1), # Thursday
(1, 1, 1), # Friday
(1, 1, 1), # Saturday
(1, 1, 1), # Sunday
]
# Penalty for exceeding the cover constraint per shift type.
excess_cover_penalties = (2, 2, 5)
num_days = num_weeks * 7
num_shifts = len(shifts)
复制代码
draw a gantt chart
In the technology stack, matplot is selected to draw Gantt charts, which is also now learning and selling.
# Generate plot image. [reference: https://www.geeksforgeeks.org/python-basic-gantt-chart-using-matplotlib]
shifts_colors = ['white', 'tab:green', 'tab:orange', 'tab:blue']
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
offset = 2
# Declaring a figure "gnt"
fig, gnt = plt.subplots()
# Setting Y-axis limits
gnt.set_ylim(0, num_employees * 15)
# Setting X-axis limits
gnt.set_xlim(1, num_days + offset)
# Setting labels for x-axis and y-axis
gnt.set_xlabel('Dates')
gnt.set_ylabel('Employees')
# Setting ticks on y-axis
# gnt.set_yticks([15, 25, 35, 45])
gnt.set_xticks(list(range(1, num_days + offset, 1)))
gnt.set_yticks(list(range(15, (num_employees + 1) * 10 + 5, 10)))
# Labelling tickes of y-axis
gnt.set_yticklabels(employees_names)
# Setting graph attribute
gnt.grid(True)
for e in range(num_employees):
for d in range(num_days):
for s in range(num_shifts):
if solver.BooleanValue(work[e, s, d]):
gnt.broken_barh([(d+1, d+2)], (10*(e+1), 9),
facecolors=(shifts_colors[s]))
plt.savefig("gantt1.png")
复制代码
result:
Summarize
- Fully understand the problem in order to find the tool
- Fortunately, this topic has already been studied. Through the topics that have been studied and the ideas of tools and solutions, we can better solve other problems.
Actual code: github.com/pascallin/N…