2021.10.16 変更あり
[勤務シフト表の作成 基礎(3)]
(https://qiita.com/ki073/items/f321a5ad33b8d385a2ea)からの続きです。そちらも合わせてご覧ください。
先に書いた「shift11.mod」は業務の割り当て回数は設定通りにできますが、日程的に偏りがでる可能性がありますので、その対策です。
少し大きなデータ(下記)を用意しました。「勤務シフト表の作成 基礎(1)」のコメント欄あるデータを使用させていただきました。
(2021.10.16 変更)
業務の割り当てが分散するように
ある連続した期間に業務Aと業務Bを1回ずつ割り当てる制約を加えてみました。
連続した5日間(period1の値)に1回(maxFreqInPeriod1の値)を超えて割り当てないようにしています。「shift11.mod」の「solve;」の上に追加すると機能します。
業務ごとに別々に制約が適用されます。出勤希望が条件を満たさない(例えば5日間に2回の希望)があった場合でも希望を優先して割り当てられますが、自動的に割り当てる勤務については連続した5日間に1回以下を満たすようになっています。
period1やmaxFreqInPeriod1を変更すことで条件を変えることができます。
下の方に「最大連続勤務日数」の設定がありますが、必要があればそちらも変更してください。
param period1 := 5;
param maxFreqInPeriod1 := 1;
s.t. constrainP1{s in Staff, d in firstDate..lastDate-(period1-1), b in Duty}:
sum{d1 in d..d+(period1-1) : not (s,d1,b) in FixOnDuty}assignShiftSchedule[d1,s,b]+min(sum{d1 in d..d+(period1-1) : (s,d1,b) in FixOnDuty}1,maxFreqInPeriod1)<=maxFreqInPeriod1;
次が、業務を区別せずに連続した日に割り当てられない制約条件です。連続したした2日間(period2の値)に1回(maxFreqInPeriod2の値)以下になるように割り当てられます。業務を区別しないことを除いて上と同じです。
param period2 := 2;
param maxFreqInPeriod2 := 1;
s.t. constrainP2{s in Staff, d in firstDate..lastDate-(period2-1)}:
sum{d1 in d..d+(period2-1), b in Duty : not (s,d1, b) in FixOnDuty}assignShiftSchedule[d1,s,b]+min(sum{d1 in d..d+(period2-1), b in Duty : (s,d1, b) in FixOnDuty}1, maxFreqInPeriod2)<=maxFreqInPeriod2;
この2つを加えることで、両方の条件を満たすシフトが完成します。
条件を満たす組み合わせが見つからなかった場合には「LP HAS NO PRIMAL FEASIBLE SOLUTION」と出力されて止まってしまいます。period1などを小さくするなど条件を変えてみてください。
###使用したデータ
「shift11.mod」のdata;より下を以下に書き換えてください。
data;
# 期間の最初の日付と最後の日付
param firstDate := 1;
param lastDate := 29;
# 業務名
set Duty := "業務A" "業務B";
# スタッフ名
set Staff := "Aさん" "Bさん" "Cさん" "Dさん"
"Eさん" "Fさん" "Gさん"
;
# 日付、業務別の必要出勤人数 defaultを1とし、それ以外の場合は後で指定
param necessary_nStaff default 1 :=
;
# スタッフ、業務別の最大割り当て勤務日数
param max_nWorkingDays default 4 :=
"Eさん" "業務A" 5
"Cさん" "業務B" 5
;
# スタッフ、業務別の最少割り当て勤務日数
param min_nWorkingDays default 4 :=
;
# 休日希望
set FixOffDuty :=
("Aさん", *)
("Bさん", *)
("Cさん", *) 4 6 11 18 25
("Dさん", *) 5 12 15 1927
("Eさん", *) 1 6 20 25 29
("Fさん", *) 1 5 11 19 22 26
("Gさん", *) 4 8 13 22 27
;
# 出勤希望
set FixOnDuty :=
("Aさん", *, "業務A") 2 12 16 22
("Aさん", *, "業務B") 4 13 18 26
("Bさん", *, "業務A") 6 13 20 26
("Bさん", *, "業務B") 2 9 21 25
;
# 最大連続勤務日数
param maxContinuousWorkDay default 5:=
;
# 期間前の連続勤務日数
param daysFromOffDutyBeforeFirstDay default 0:=
;