以下是完整代码和注释
'''manpower_planning:包含两个目标函数分别为尽量减少规划期内的裁员总数和最大限度地降低规划期内因培训、超员、裁员而造成的总成本 2024/5/22 by sadbooooii'''
import numpy as np
import pandas as pd
import gurobipy as gp
from gurobipy import GRB
# 参数
years = [1, 2, 3]
skills = ['s1', 's2', 's3'] # s123分别代表unskilled、semi_skilled、skilled的技能熟练度的工人
curr_workforce = {'s1': 2000, 's2': 1500, 's3': 1000} # 规划期内开始时技能s123的现有工人
demand = {
(1, 's1'): 1000,
(1, 's2'): 1400,
(1, 's3'): 1000,
(2, 's1'): 500,
(2, 's2'): 2000,
(2, 's3'): 1500,
(3, 's1'): 0,
(3, 's2'): 2500,
(3, 's3'): 2000
} # 第t年技能s123所需要的人力数量
rookie_attrition = {'s1': 0.25, 's2': 0.20, 's3': 0.10} # 工作第一年内离职工人的百分比
veteran_attrition = {'s1': 0.10, 's2': 0.05, 's3': 0.05} # 工作第一年后离职工人的百分比
demoted_attrition = 0.50 # 降职后离开公司的员工的百分比
max_hiring = {
(1, 's1'): 500,
(1, 's2'): 800,
(1, 's3'): 500,
(2, 's1'): 500,
(2, 's2'): 800,
(2, 's3'): 500,
(3, 's1'): 500,
(3, 's2'): 800,
(3, 's3'): 500
} # 计划年份内可雇佣的拥有技能s123的工人的最大数量
max_overmanning = 150 # 计划年份内超员工人的最大数量
max_parttime = 50 # 计划年份内拥有技能s123的兼职工人的最大数量
parttime_cap = 0.50 # 兼职工人相对于全职工人的生产率
max_train_unskilled = 200 # 计划年份内可培训的非熟练工人的最大数量
max_train_semiskilled = 0.25 # 计划年份内可培训的半熟练工人相对于熟练工人的比例
training_cost = {'s1': 400, 's2': 500} # 拥有技能s12的工人通过技能培训成为s23的费用
layoff_cost = {'s1': 200, 's2': 500, 's3': 500} # 解雇拥有技能s123的工人的成本
parttime_cost = {'s1': 500, 's2': 400, 's3': 400} # 指派一名拥有技能s123的工人从事兼职工作的成本
overmanning_cost = {'s1': 1500, 's2': 2000, 's3': 3000} # 拥有技能s123的工人总的年度超员成本
# 部署决策变量和约束
manpower = gp.Model('Manpower planning')
hire = manpower.addVars(years, skills, ub=max_hiring, name="Hire") # 第t年要雇佣的具有技能s123的工人数量
part_time = manpower.addVars(years, skills, ub=max_parttime,
name="Part_time") # 第t年工作的具有技能s123的兼职工人人数
workforce = manpower.addVars(years, skills, name="Available") # 第t年拥有技能s123的工人数量
layoff = manpower.addVars(years, skills, name="Layoff") # 第t年被解雇的技能s123的工人数量
excess = manpower.addVars(years, skills, name="Overmanned") # 第t年超员的拥有技能s123的工人数量
train = manpower.addVars(years, skills, skills, name="Train") # 第t年拥有技能s再接受技能培训为s*的工人数量
# 第t年的可用劳动力等于前一年的劳动力,近期招聘的人数,晋升和降级的人数(计入自然减员)减去裁员和调离的人数(1)
Balance = manpower.addConstrs(
(workforce[year, level] == (1-veteran_attrition[level])*(curr_workforce[level] if year == 1 else workforce[year-1, level])
# 如果year等于1,则使用初始工人数量curr_workforce[level],否则使用前一年的可用工人数量workforce[year-1, level]
+ (1-rookie_attrition[level])*hire[year, level] + gp.quicksum((1- veteran_attrition[level])* train[year, level2, level]
-train[year, level, level2] for level2 in skills if level2 < level)
+ gp.quicksum((1- demoted_attrition)* train[year, level2, level] -train[year, level, level2] for level2 in skills if level2 > level)
- layoff[year, level] for year in years for level in skills), "Balance")
# 计算现役的拥有技能s123的工人晋升和降级的情况
# 第t年培训的非熟练工人不能超过限额,非熟练工人不能立即转变为熟练工人(2)
UnskilledTrain1 = manpower.addConstrs((train[year, 's1', 's2'] <= max_train_unskilled for year in years), "Unskilled_training1")
UnskilledTrain2 = manpower.addConstrs((train[year, 's1', 's3'] == 0 for year in years), "Unskilled_training2")
# 第t年培训的半熟练工人不能超过熟练工人的四分之一(3)
SemiskilledTrain = manpower.addConstrs((train[year,'s2', 's3'] <= max_train_semiskilled * workforce[year,'s3'] for year in years), "Semiskilled_training")
# 第t年超额的拥有技能s123的工人人数不能超过限额(4)
Overmanning = manpower.addConstrs((excess.sum(year, '*') <= max_overmanning for year in years), "Overmanning")
# 第t年的可用劳动力等于所需工人数加上多出来的工人和兼职人数
Demand = manpower.addConstrs((workforce[year, level] ==
demand[year,level] + excess[year, level] + parttime_cap * part_time[year, level]
for year in years for level in skills), "Requirements")
# 两个目标函数
# 最小化被解雇工人的人数
obj1 = layoff.sum()
manpower.setObjective(obj1, GRB.MINIMIZE)
# 最小化所有就业工人的总成本和再培训成本
obj2 = gp.quicksum((training_cost[level]*train[year, level, skills[skills.index(level)+1]] if level < 's3' else 0)
+ layoff_cost[level]*layoff[year, level]
+ parttime_cost[level]*part_time[year, level]
+ overmanning_cost[level] * excess[year, level] for year in years for level in skills)
manpower.optimize()
# 雇佣计划
rows = years.copy()
columns = skills.copy()
hire_plan = pd.DataFrame(columns=columns, index=rows, data=0.0)
for year, level in hire.keys():
if (abs(hire[year, level].x) > 1e-6):
hire_plan.loc[year, level] = np.round(hire[year, level].x, 1)
print(hire_plan)
# 培训和降级计划
rows = years.copy()
columns = ['{0} to {1}'.format(level1, level2) for level1 in skills for level2 in skills if level1 != level2]
train_plan = pd.DataFrame(columns=columns, index=rows, data=0.0)
for year, level1, level2 in train.keys():
col = '{0} to {1}'.format(level1, level2)
if (abs(train[year, level1, level2].x) > 1e-6):
train_plan.loc[year, col] = np.round(train[year, level1, level2].x, 1)
print(train_plan)
# 裁员计划
rows = years.copy()
columns = skills.copy()
layoff_plan = pd.DataFrame(columns=columns, index=rows, data=0.0)
for year, level in layoff.keys():
if (abs(layoff[year, level].x) > 1e-6):
layoff_plan.loc[year, level] = np.round(layoff[year, level].x, 1)
print(layoff_plan)
# 兼职工人雇佣计划
rows = years.copy()
columns = skills.copy()
parttime_plan = pd.DataFrame(columns=columns, index=rows, data=0.0)
for year, level in part_time.keys():
if (abs(part_time[year, level].x) > 1e-6):
parttime_plan.loc[year, level] = np.round(part_time[year, level].x, 1)
print(parttime_plan)
# 超员雇佣计划
rows = years.copy()
columns = skills.copy()
excess_plan = pd.DataFrame(columns=columns, index=rows, data=0.0)
for year, level in excess.keys():
if (abs(excess[year, level].x) > 1e-6):
excess_plan.loc[year, level] = np.round(excess[year, level].x, 1)
print(excess_plan)