2. 5 チューターに纏わる勤務表設計例

1 制約仕様

看護師25名うち新人4名の3交代で、準夜・深夜それぞれ3名、
新人に対して教育担当が4名います。

 (1) 新人の夜勤(準夜、深夜)の時は教育担当が少なくともだれか1名出勤していて欲しい

 (2) 新人の日勤者数によって、新人以外の日勤者数を可変にしたい
   (例えば、新人1名なら新人以外10名、新人2名なら新人以外11名、新人3名なら新人以外10名と変則)


2 制約設計



2.3 外部制約

(1)(2)を同時に実装しています。




チューターは、スタッフプロパティで以下のように定義されています。

どちらも列に関する制約ですので、毎日(全日)制約することにします。
しかしながら、前月(スケジュール開始日より前)は、制約していないので、今月(スケジュール開始日以降)についてだけ制約しています。

また、新人をスキャンするために、全員集合である全スタッフも定義を追加しています。
チューター集合は、解をチェックするのに見易くするためだけに定義しており、制約上の意味はありません。

各日について、新人の日勤勤務、深夜勤務、準夜勤務をvectorに集めます。
同じように新人以外についてもvectorに集めます。
もしも新人が夜勤だったら、チューターのうち最低1人は、同じシフトに入って面倒を見る というのがチューターに関する制約になります。
これは、if (A) then B;という制約パターンで、論理式で表現でき、!A ||B になります。つまり $Or($Inv(A) ,B); の形にすればよいです。これが28、29行目の制約になります。28行目は、新人が1人でも深夜勤務していたら(=$Or(VS1))、最低1人以上(=$Or(VS2))のチューター(4人のうち1人以上)が深夜勤務すること、という制約になります。
29行目は、同じく準夜に関する制約です。(等価な制約は、GUIペア強制でも書けますが、人数が多いと面倒です。)

次に(2)新人以外の日勤者数に関する制約について考えてみます。これは、単純でないので外部制約でないと無理です。


現状、次のようにGUIで制約されている部分と重なりますので、とりあえず青部の制約は、ソフト制約レベルを1にして外しておきます。


これも 新人が1人日勤なら、新人以外は10人 というように 同じif (A) then B;という制約パターンで、論理式で表現でき、!A ||B パターン.
で書けます。
新人は4人いるので、全ての場合について記述しましょう。

新人1人以下の場合は、新人以外10人、
新人2人の場合は、新人以外11人
新人3人以上の場合は、新人以外10人
という風に制約します。

なお、通常、日勤者数を固定値にすると、「解がない」事態になりますが、このプロジェクトの場合は、公休数を可変にすることでそれを可能にしているようです。

その月固有の日は、下のカレンダで定義するとよいでしょう。


for (day in 全日){
        if (day>=スケジュール開始日){
                vector V1,V2;
                vector VS1,VJ1,VS2,VJ2;
                for (人 in 全スタッフ){
                        if (人 in 新人){
                                V1.push_back(X[人][day][N]);//新人日勤勤務者
                                VS1.push_back(X[人][day][S]);//新人深夜
                                VJ1.push_back(X[人][day][J]);//新人準夜

                        }else {
                                V2.push_back(X[人][day][N]);//新人以外日勤勤務者
                                if (人 in チューター){
                                        VS2.push_back(X[人][day][S]);//チュータ深夜
                                        VJ2.push_back(X[人][day][J]);//チュー他準夜
                                }

                        }
                }

                if (day in 日勤11人日 || day in 日勤10人可能日){
        //              print(V1);
                        
                        HardA: $Or($Inv($SeqExpr(0,1,0,V1)),$SeqExpr(10,10,2,V2));//if(新人数<=1) 新人以外=10人
                        HardB: $Or($Inv($SeqExpr(2,2,2,V1)),$SeqExpr(11,11,2,V2));//if(新人数==2) 新人以外=11人
                        HardC: $Or($Inv($SeqExpr(3,0,1,V1)),$SeqExpr(10,10,2,V2));//if(新人数>=3) 新人以外=10人
                }
                チュータ制約深夜: $Or($Inv($Or(VS1)),$Or(VS2)); 
                チュータ制約準夜: $Or($Inv($Or(VJ1)),$Or(VJ2)); 
        }       
}


なお、上記はハード制約としましたが、制約が厳しい場合は解がないかもしれません。
そこで、ソフト制約化した記述を下に示します。ソフト制約化する場合の注意点は、エラーをカウントすることになるので、上記と論理が反対になることです。
また、カウントに関する制約が満足できないとそのカウントそのものの制約も外れてしまうので、ハード制約で必ずこれ以上は確保するというガードを設けることも必要になります。

for (day in 全日){
        if (day>=スケジュール開始日){
                vector V1,V2;
                vector VS1,VJ1,VS2,VJ2;
                for (人 in 全スタッフ){
                        if (人 in 新人){
                                V1.push_back(X[人][day][N]);//新人日勤勤務者
                                VS1.push_back(X[人][day][S]);//新人深夜
                                VJ1.push_back(X[人][day][J]);//新人準夜

                        }else {
                                V2.push_back(X[人][day][N]);//新人以外日勤勤務者
                                if (人 in チューター){
                                        VS2.push_back(X[人][day][S]);//チュータ深夜
                                        VJ2.push_back(X[人][day][J]);//チュー他準夜
                                }

                        }
                }

                if (day in 日勤11人日 || day in 日勤10人可能日){
        //              print(V1);
                        Hard:$SeqLE(9,0,V2);//新人以外9人以上確保は絶対条件(GUIでも書ける)
                        
                        SoftA74: $Inv($Or($Inv($SeqExpr(0,1,0,V1)),$SeqExpr(10,10,2,V2)));//if(新人数<=1) 新人以外=10人
                        SoftB74: $Inv($Or($Inv($SeqExpr(2,2,2,V1)),$SeqExpr(11,11,2,V2)));//if(新人数==2) 新人以外=11人
                        SoftC74: $Inv($Or($Inv($SeqExpr(3,0,1,V1)),$SeqExpr(10,10,2,V2)));//if(新人数>=3) 新人以外=10人
                }
                チュータ制約ソフト深夜22: $Inv($Or($Inv($Or(VS1)),$Or(VS2)));   
                チュータ制約ソフト準夜22: $Inv($Or($Inv($Or(VJ1)),$Or(VJ2)));   
        }       
}




2.8 希望を入力する



2.9 求解


ソフト制約の場合です。外部制約が、レベル7.4、2.2 で出ています。2.2は、最も優先度が低い制約になります。下の右ペインで見て分かるように5個のエラーが残っています。
つまり、5回のチューターなしの新人勤務が生じているということです。優先順位を変更すれば、この部分について改善することは可能ですが、代わりに他の部分にしわ寄せが来るでしょう。

2.10 解


全体の解です。日勤者数については、優先度が高いのが功を奏し、全て満足しています。



チュータについては、やはり、数個満足出来ていません。


2.11 名前を変更して保存する

名前をつけて保存します。必ず名前をつけて保存で保存してください。本プロジェクトの場合、プロジェクトファイル以外に外部制約ファイルを使います。
外部制約ファイルは、プロジェクト名.txt になっています。(名前をつけて保存すると外部制約ファイルも同時に生成コピーされますので、外部制約ファイルの存在を気にしなくてもよくなります。)