2020年2月15日土曜日

夜勤新人月が終わったら

夜勤新人月が終わった後、また来年も使うかもしれませんので、メンテナンスしておきましょう。

実は、訓練期間が、下のようにブランクだと、右ペインに示されるようにエラーになってしまいます。
原因は、行制約で訓練期間では、2回夜勤を行うという制約をハード制約として記述したからです。
単純にソフト制約とすれば回避できますが、ソフトエラーとしてカウントされるのが面白くない、という方もおられでしょう。それが今日のテーマです。


訓練期間が定義されていないときは、制約そのものをDisableしてしまえばよい、という発想が浮かびます。下の最後の方、 夜勤新人Disable()が制約をDisableする記述です。僅か3行で済みます。

スタッフプロパティで夜勤新人を設定、上で期間設定を行う、ということをマニュアルに書いておけば、来年もまた使えます。

import sc3

def 夜勤回数設定sub(person):
    st=staffdef[person]+'夜勤回数を制約'
    sc3.print(st+'します。\n')
    max=-1
    min=0
    if person in 夜勤回数最大属性:
        max=夜勤回数最大属性[person]
    if person in 夜勤回数最小属性:
        min=夜勤回数最小属性[person]

    sc3.print(str(max)+str(min)+'\n')
    vlist=[]
    for day in 今月:
        v=sc3.GetShiftVar(person,day,'入り')
        vlist.append(v)
    sc3.AddSoft(sc3.SeqError(min,max,3,vlist),st,5)

def 夜勤回数設定():
    for person in 夜勤あり:
        夜勤回数設定sub(person)
   


def 公休数設定():
    for person in 全スタッフ:
        max=-1
        min=0
        if person not in 公休数属性最大:
            continue
        st=staffdef[person]+'公休数を制約'
        sc3.print(st+'します。\n')
        if person in 公休数属性最大:
                max=公休数属性最大[person]#float
        if person in 公休数属性最小:
                min=公休数属性最小[person]
        max *=2
        min *=2
        if isinstance(max, float) and not max.is_integer():
            sc3.print('公休数設定で、maxが整数型ではありません。')
            continue
        if isinstance(min, float) and not min.is_integer():
            sc3.print('公休数設定で、minが整数型ではありません。')
            continue
        max =int(max) #float to int
        min =int(min) #float to int
        sc3.print(str(max)+str(min)+'\n')
        vlist=[]
        for day in 今月:
            v=sc3.GetShiftVar(person,day,'公休')#公休は2回分カウント
            vlist.append(v)
            vlist.append(v)
            v=sc3.GetShiftVar(person,day,'早半休')#早半休は1回
            vlist.append(v)
            v=sc3.GetShiftVar(person,day,'半休')#半休は1回
            vlist.append(v)
        sc3.AddSoft(sc3.SeqError(min,max,4,vlist),st,6)

def 土曜以外の半休抑制():
    for person in 全スタッフ:
        for day in 土曜でない今月:
            ts=shift_schedules[person][day][0]
            if ts !='半休':
                v=sc3.GetShiftVar(person,day,'半休')
                st=staffdef[person]+'土曜以外の半休抑制'
                sc3.AddSoft(~v,st,1)

def 夜勤新人Disable():
    if len(夜勤新人訓練期間)==0:
        sc3.ConstraintEnable('夜勤新人.夜勤新人は、夜勤訓練期間に2回訓練を行う',False)


夜勤回数設定()
公休数設定()
土曜以外の半休抑制()
夜勤新人Disable()
 
夜勤新人訓練期間がなくても意図通り動作しました。
 
期間を日月火水木にした例です。
 

2020年2月14日金曜日

夜勤新人の最初の2回は、プリセプター付

4月5月は、看護師の移動が多い月です。看護師としてはベテランでも、
その病棟にとっては夜勤新人という方が多いかもしれません。そのような状況を想定し、頑張って制約化してみましょう。 (勿論、制約化しないで予定として入れてしまうのもアリです。)

仕様は、最初の2回は、先輩看護師付で、以降は、通常ルーチンとします。最初の2回の期間を考えて、夜勤新人訓練期間を定義します。月の前半を訓練期間としましょう。


月前半 訓練期間
 夜勤新人が夜勤する場合は、2+1人勤務
月後半 通常勤務
夜勤新人が入っても2人勤務

今月を全体集合として、上の補集合を求めましょう。
スタッフプロパティで夜勤新人を定義します
夜勤新人でないグループを定義します。
夜勤新人は、訓練期間に2回訓練を行う制約を追加します。
列制約です。
解です。2人の夜勤新人が訓練期間中に各々2回夜勤を行っています。
この期間は、プリセプティ分、夜勤者が増えて3人となります。月後半は、通常ルーチンに戻り、
夜勤新人に関わらず2人夜勤となります。かなり複雑な仕様ですが、GUIのみで記述できました。
 
 


2020年2月13日木曜日

男同士の夜勤をさせない

ペア設定で書きたくなりますが、A/B とも同じ集合(男)だと、動作しないと思います。
これは、ペア設定ではなく、

Σ男 <=1

と制約すれば、2名以上の男性が勤務することはありません。よって、スタッフプロパティで、性別を指定し、次のように記述します。


2020年2月12日水曜日

2020年2月10日月曜日

xbyakによる実装

Xbyakは、Intelのアセンブリ言語命令を実行時に生成するC++のライブラリです。
今の時代、LLVMがあるので、C++コードのJITコンパイルも可能になっており、その必要性は狭まってはいますが、Xbyakで記述した方が手軽な場合もあります。

例えば、IntelのMKL-DLLという深層学習用の高速化ライブラリがありますが、そのエンジンには、Xbyakが使われているそうです。

https://blog.cybozu.io/entry/2019/04/15/170000

またスパコンCPU aarch64(ARMの64ビットアークテクチャ)にも移植されているようです。

Xbyakの概要はこちら

今回の使用目的は、機械・深層学習用ではなくて、AL4の高速化です。AL4は、数理的ソルバーですが、部分問題を解く必要があり、そのための新しいアイデアに対する実装に使用します。
コンパイル時定数であれば、C++テンプレートでC++コンパイラが勝手に最適化してくれますが、RUNTIME時定数では、テンプレートは何の役にも立ちません。RUNTIME時定数というメリットを最大限使って高速化を図ります。Xbyakの使用は、初めてで、SSE2も初めてなので、初めてのXbyakによるSSE2ということで、実装中のコードを使用して、この先書きたいと思います。

2020年2月9日日曜日

リーダ1日以上4日以下

下記は、スニペットです。GUIだけで記述できました。







平準化のため、各スタッフ6日以下としていますが、Pythonを使えば、この部分もダイナミック化
が可能です。ダイナミック化とは、動的に例えば6という数字を決定することです。元々、6というのは、

 リーダ回数=月の日数/リーダ可能者人数+1

で割り出したはずです。ところが、月の日数は変わるし、リーダ可能者も、スタッフプロパティに現れなくて出来ない人(実習等で予定書き込み)もいるかもしれません。その場合でも、予定を集約すれば、より精度の高い平準化が可能になります。同時に、将来のリーダ可能者の増減にも自動で対応可能となります。ロバストな勤務表づくりの要点は、そういった将来の人数の増減まで考えることです。

2020年2月7日金曜日

予定の入っていないところだけ制約する

前回から続きプロジェクト実装です。
確かに、公休数11.5を実現するのに、全休10回、半休3回(長坂さん)でよい雰囲気です。

しかし、例えば横山さんの公休数11を実現するために、全休8日、半日が6日もアサインがされてしまっていて問題です。半日には、何ら制約が入っていないために、こういう解が出てしまいます。

そこで、土曜日以外は、なるべく半日をアサインしないように制約したくなるのですが、元々予定が入っているところは、意味がないので外すことにします。(ソフト制約は、ソルバーにとって重いのでなるべく少なくしたいという意図もあります。)
土曜日だけ半日→土曜以外は半日禁止→今月土曜以外は、半日禁止 というロジックにより

def 土曜以外の半休抑制():
    for person in 全スタッフ:
        for day in 土曜でない今月:
            ts=shift_schedules[person][day][0]
            if ts =='':
                v=sc3.GetShiftVar(person,day,'半休')
                st=staffdef[person]+'土曜以外の半休抑制'
                sc3.AddSoft(~v,st,1)
shift_schedules[person[day][0] に予定が入っていればシフトが入ってきます。
空白のときだけ、半休がアサインされ、レベル1のペナルティを与えるコードになっています。
このコードをEnableすると、土曜日だけ半休がアサインされて下の結果となります。

 
GUIでは、静的制約しかできませんが、Pythonを使えば、上のようにダイナミックに制約することが出来ます。

2020年2月6日木曜日

公休数10.5日をどう記述する?

時間制約でも記述できます。この場合、スタッフプロパティで 8.5/9/9.5/10/10.5/11...に対応するプロパティを用意して、それぞれ時間制約で制約すればよいです。

Pythonで記述すること考えたのですが、プロパティファイル生成時、浮動小数に対応していないという問題が発生しました。そこで、以下のように正規表現を使って浮動小数に対応しました。(ソルバ C++)を変更しました。(対応Versionは、127C)



bool check_int(std::string str)
{
#ifdef FEB052020
 regex re("[0-9]+(\.[0-9]*)?|[0-9]+");
 if (regex_match(str, re)) {
  return true;
 }else return false;
#endif
 
127C以降では、次のようにPythonで記述できます。
 
 半休は、1回、公休(1日休み)は、2回カウントしてやると、0.5が整数になります。なので、min/maxを2倍にしてやります。これで時間制約の記述なしにpythonで、浮動小数の公休数に対応しました。
なお、float型かint型かの判定をしたあと、float型で,かつまだ浮動小数があるならメッセージを出しています。
 
def 公休数設定():
    for person in 全スタッフ:
        max=-1
        min=0
        if person not in 公休数属性:
            continue
        st=staffdef[person]+'公休数を制約'
        sc3.print(st+'します。\n')
        if person in 公休数属性:
                max=公休数属性[person]#float
                min=公休数属性[person]
        max *=2
        min *=2
        if isinstance(max, float) and not max.is_integer():
            sc3.print('公休数設定で、maxが整数型ではありません。')
            continue

        max =int(max) #float to int
        min =int(min) #float to int
        sc3.print(str(max)+str(min)+'\n')
        vlist=[]
        for day in 今月:
            v=sc3.GetShiftVar(person,day,'公休')#公休は2回分カウント
            vlist.append(v)
            vlist.append(v)
            v=sc3.GetShiftVar(person,day,'早半休')#早半休は1回
            vlist.append(v)
            v=sc3.GetShiftVar(person,day,'半休')#半休は1回
            vlist.append(v)
        sc3.AddSoft(sc3.SeqError(min,max,4,vlist),st,6)

2020年2月3日月曜日

2020年2月2日日曜日

ORのレクチャー

目的関数値とか、スケジュールナースのマニュアルには、時々OR用語が出てきますが、
良い、解説がありました。

http://www.robot.t.u-tokyo.ac.jp/dcm/lec_opt/lec01.pdf

http://www.robot.t.u-tokyo.ac.jp/dcm/lec_opt/lec02.pdf

http://www.dais.is.tohoku.ac.jp/~shioura/teaching/mp11/mp11-06.pdf




見て分かるように、ナップザック問題や最短経路問題は、ナーススケジューリングにおいても重要な部分問題であり、この性能の良し悪しが全体のボトルネックになります。数理最適化において最も重要な解法は、分岐限定法です。分岐限定法は、ポイントが二つあり、ノードの選択とと切除平面です。前者については、Reserchgateの回答がエッセンスです。
https://www.researchgate.net/post/Branch_and_Bound_Algorithm_How_to_choose_variables_in_case_of_more_than_one_fractional_values

切除平面については、色々な方法がありますが、最も基本的なものは、小数カットと呼ばれる方法です。

http://www.orsj.or.jp/~archive/pdf/bul/Vol.48_12_936.pdf
http://infoshako.sk.tsukuba.ac.jp/~maiko/mathpro/n_note2-2.pdf
http://dopal.cs.uec.ac.jp/okamotoy/lect/2013/opt/handout06.pdf
http://www.msi.co.jp/nuopt/glossary/term_167e2968d79e57b147ffb376f70159d277feacb7.html