前提・実現したいこと
python初心者です。
pulpを用いてシフトの作成を行いたいと思っています。
アルバイトが3人、日にちが1日、シフトパターンが4つ、時刻が1時間ごとに設けられており、それぞれ必要人数は2人です。アルバイトの希望出勤日がわかっており、0の場合は休み、1の場合は出勤可能を表しています(今回はすべて1ですが)。
この問題の目的は希望出勤日数(希望出勤日の総和)と実際に勤務した日の総和を最小にすることです。
制約として、以下の3つを考えています。
・実際に働いた日数が希望出勤日数を超えない制約
・各時刻の勤務人数が必要人数以上である制約
・入ることのできるシフトは1つだけである制約
これらのことを踏まえ作成してみたプログラムは以下の通りです。
python
1import numpy as np 2import pandas as pd 3from pulp import LpBinary, LpMinimize, LpProblem, LpVariable, lpSum, value 4 5#アルバイトの集合 6n_member = 3 7member = pd.Series(f"member{m+1}" for m in range(n_member)) 8 9#日にちの集合 10n_day = 1 11day = pd.Series(f"day{d+1}" for d in range(n_day)) 12 13#時刻の集合 14n_time = 9 15time = pd.Series(f"{t+12}時" for t in range(n_time)) 16 17#シフト番号 18n_shiftnumber = 4 19shiftnumber = pd.Series(f"shift{sn+1}" for sn in range(n_shiftnumber)) 20 21#シフトの一覧 22shift = pd.DataFrame([ 23 [1, 1, 1, 1, 1, 1, 0, 0, 0], 24 [0, 0, 0, 0, 1, 1, 1, 1, 1], 25 [1, 1, 1, 0, 0, 1, 1, 1, 1], 26 [1, 1, 1, 1, 0, 0, 1, 1, 1], 27 ], 28 index=shiftnumber, 29 columns=time 30 ) 31 32#必要人数 33need = pd.DataFrame([[2, 2, 2, 2, 2, 2, 2, 2, 2]], index=day, columns=time) 34 35#希望出勤日 36kibou = pd.DataFrame([[1], 37 [1], 38 [1]], index=member, columns=day) 39 40 41#モデルの作成 42prob = LpProblem(sense=LpMinimize) 43 44#変数 45x = [[[LpVariable(f"x{m}{d}{t}", cat=LpBinary) for t in shift] 46 for d in day] for m in member] 47 48 49#目的関数 50for m in range(member.size): 51 #希望出勤日数 52 ks = kibou.iloc[m].sum() 53 #実際に働いた日数 54 actual = lpSum(x[m][d] for d in range(day.size)) 55 #希望出勤日数と実際に働いた日数の差 56 prob += ks - actual 57 #(制約1)実際に働いた日数が希望出勤日数を超えない制約 58 prob += actual <= ks 59 60 61#制約条件 62#(制約2)必要最低人数を満たす制約 63for d in range(need.index.size): 64 for t in range(need.index.size): 65 prob += lpSum(x[m][d][t] for m in range(member.size)) >= need.iloc[d, t] 66 67#(制約4)各メンバーが1日に取りうるシフトは必ず1つ 68for m in range(member.size): 69 for d in range(day.size): 70 prob +=lpSum(x[m][d][t] for t in range(shift.index.size)) == 1 71 72prob.solve() 73 74 75scheduling = pd.DataFrame(index=member, columns=day) 76for m in range(member.size): 77 for d in range(day.size): 78 for i,j in enumerate(shift.index): 79 if value(x[m][d][i]): 80 scheduling.iloc[m, d] = j 81scheduling.to_csv("scheduling.csv") 82
私の欲しい答えとしては、shift1, shift2, shift4の3つがそれぞれのメンバーに振り分けられるようにしたいのですが、全員がshift1という結果になってしまいます。
つたない説明で申し訳ありません。どうすればよいかご教授のほどよろしくお願いします。
これに加えて、今後目的関数に、もし出勤可能な人数が必要人数を下回る場合に、ダミーのアルバイトを不足しているシフトにいれ、そのダミーの総和を加えるということもやりたいと思っています。よろしければこちらも答えていただけると幸いです。