2020年6月30日火曜日

有給日数の最小化

有給も含めて勤務計算を行っている職場では、経営上の観点からは、できるだけ少ないほうが望ましい。そのようなスクリプトです。この例の職場では、半日単位で、有給、公休があります。経営的には、最小有給を取得させた上で、公休で済ませることができればその方が望ましいということです。
個人単位では、行制約で、有給0を目指せばよいのですが、目指したいのは、全体の有給を最小することです。そのためには、全員の有給を加算して、 その和が0になるようにする必要があります。これは、GUIでは書けないので、Pythonの出番です。元々有給設定しているスクリプトに僅か2-3行追加で実現可能です。 予め個人単位で、今月は、最小これ以上取得するというmin値が決まっているので、それより小さい数を目指すことは、意味がありません。ですので、各個人の最小値の和が目指すべき最小値となります。後は、ソフトレベル(ここでは5)の重みを変えてやれば、好みの優先度で最小化が実現できます。
#post operation
def 有給日数設定OneShift():
    min_sum=0;
    vlist_sum=[];
    for person in 全スタッフ:
        max=-1
        min=0

        st=staffdef[person]+' 有給日数を制約'
        sc3.print(st+'します。\n')
        if person in 有給日数最大:
                max=有給日数最大[person]#float
        if person in 有給日数最小:
                min=有給日数最小[person]#float
        
                    

        max *=2
        min *=2
        min_sum+=min;
        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 今月:#午前・午後の日勤と有給taskと有給シフトをカウントする 有給シフトは、2回分
            v=sc3.GetTaskVar(person,day,0,'有給');#AM
            vlist.append(v)
            v=sc3.GetTaskVar(person,day,1,'有給');#PM
            vlist.append(v)

        sc3.AddSoft(sc3.SeqError(min,max,1,vlist),st,7)
        vlist_sum+=vlist;
    sc3.AddSoft(sc3.SeqError(min_sum,min_sum,10,vlist_sum),'Total有給の極小化',5)#重みは調整ください

#Main rountine
有給日数設定OneShift()


2020年6月29日月曜日

Python Post処理

解生成後に、解を整形したり、解を統計解析したい場合があります。そんな用途に、ポスト処理用Pythonを用意しました。

例えば、フェーズオブジェクトを用いた制約系では、次のような解出力になります。3フェーズ/Day出力となります。


しかし、実際にユーザが管理しているのは、次のようなExcelフォーマットです。このフォーマットはユーザ毎に違います。SC3のExcel出力オプションでなんとかなるレベルではありません。


このGAPを埋めるのに、Excelマクロを使ったり、Openpyxl等を使った処理が必要になります。残念ながら、Openpyxlは、巨大すぎて実装できていないので、次善の策として、Pythonで整形したCSVファイルを出力することにしました。

Pythonソースは、以下です。
#post operation
def get_day_index(day):
    return day-今月[0]+1 #名前項目分
    
def draw_row(person,day,row0,row1):
    phases=3
    t0=task_solution[person][day*phases+0]
    t1=task_solution[person][day*phases+1]
    t2=task_solution[person][day*phases+2]
    d=get_day_index(day)

    #debug sc3.print(str(d)+' '+str(len(row0))+'\n')

#勤務処理
    if t0=='日T' and t1=='日T':
        if day in 休勤日今月:
            row0[d]='休勤'
        else :
            row0[d]='日勤'
    elif t0=='日T' and t1=='有給':
        row0[d]='後有'
    elif t1=='日T' and t0=='有給':
        row0[d]='前有'
    elif t0=='有給' and t1=='有給':
        row0[d]='有休'
    elif t0=='公休' and t1=='公休':
        row0[d]='休み'
    elif t0=='日T' and t1=='公休':
        row0[d]='午前'
    elif t1=='日T' and t0=='公休':
        row0[d]='午後'
    elif t0=='希望休み' and t1=='希望休み':
        row0[d]='希休'
    else:
        row0[d]='M休'
    

#属性処理
    if t2 !='公休':
        if day in 土:
            row1[d]='土拘'
        else :
            row1[d]='拘束'
        if shift_schedules[person][day]=='希望休み扱い':
            if row0[d] !='希休':
                row1[d]='希休'



def post_main():
    sc3.print('\n\n*********ポスト処理を実行中です。*************\n')
    sol_list=[]
    for person in 全スタッフ:
        row0=[]
        row1=[]
        row0.append(staffdef[person])
        row1.append('')
        for day in 今月:
            row0.append('') #配列確保
            row1.append('') #配列確保
            draw_row(person,day,row0,row1)
        sol_list.append(row0)
        sol_list.append(row1)
#Write csv file
    sc3.print('CSVファイルを生成中です。\n')
    os.chdir(project_file_path)
    file_path=project_file_path+'/this_month_solution.csv'
    with open(file_path, 'w', newline='') as file_path:
        writer = csv.writer(file_path)
        writer.writerows(sol_list)
    sc3.print('CSVファイルを生成しました。\n')
    sc3.print('********ポスト処理を終了します。*******************\n')

#Main rountine

勤務日数設定OneShift()
有給日数設定OneShift()
週あたりの勤務日数設定OneShift()



このPythonソース中、def post_main(): がポスト処理で使用されるルーチンです。構文チェックは、
制約実行時に行われるので、構文エラーは、制約実行段階でチェックされます。制約実行時、解は未だありませんが、インタプリタの利点で、実行までその存在チェックはされないので、なくても問題ありません。

ポスト処理時は、制約実行のmain routineは、カットされます。このために、次のような記述構造としてください。


解は、shift_solution/task_solutionとして、2次元リストで得られます。これを解析してCSV出力しています。


処理内容としては、作業フォルダをプロジェクトファイルにした後、解をユーザフォーマットに変換して、CSV処理するだけです。ユーザは出力されたCSVをExcelで読み、そのシートを貼り付けるだけで済みます。
ポスト処理時のソースは、以下のソース全体(ポスト)で見ることが出来ます。(ReadOnlyです)




#staffdef
staffdef=['スタッフ1','スタッフ2','スタッフ3','スタッフ4']
#daydef
制約開始日=6
制約終了日=35
表示開始日=0
daydef=['2020-05-26','2020-05-27','2020-05-28','2020-05-29','2020-05-30','2020-05-31','2020-06-01','2020-06-02','2020-06-03','2020-06-04','2020-06-05','2020-06-06','2020-06-07','2020-06-08','2020-06-09','2020-06-10','2020-06-11','2020-06-12','2020-06-13','2020-06-14','2020-06-15','2020-06-16','2020-06-17','2020-06-18','2020-06-19','2020-06-20','2020-06-21','2020-06-22','2020-06-23','2020-06-24','2020-06-25','2020-06-26','2020-06-27','2020-06-28','2020-06-29','2020-06-30']
#staffcollection
全スタッフ=[0,1,2,3]
正社員=[0,1]
パート=[2,3]
Work=[0,1,2,3]
日T=[0,1,2,3]
西拘束=[0,1,2,3]
拘束=[0,1,2,3]
有給=[0,1,2,3]
公休=[0,1,2,3]
希望休み=[0,1,2,3]
NoTaskVar=[0,1,2,3]

#daycollection
今月=[6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]
日=[5,12,19,26,33]
月=[6,13,20,27,34]
火=[0,7,14,21,28,35]
水=[1,8,15,22,29]
木=[2,9,16,23,30]
金=[3,10,17,24,31]
土=[4,11,18,25,32]
全日=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]
祝=[]
振=[]
Dr1人木金=[9,16,24,31]
特別休業=[]
パート給与計算1週目=[5,6,7,8,9,10,11]
パート給与計算2週目=[12,13,14,15,16,17,18]
パート給与計算3週目=[19,20,21,22,23,24,25]
パート給与計算4週目=[26,27,28,29,30,31,32]
パート給与計算5週目=[33,34,35]
増員火曜日=[7,35]
特別全員出勤日=[]
稼働日=[0,1,2,3,4,6,7,8,9,10,11,13,14,15,16,17,18,20,21,22,23,24,25,27,28,29,30,31,32,34,35]
制約開始日一日前=[5]
制約開始日二日前=[4]
制約開始日三日前=[3]
制約開始日四日前=[2]
制約開始日五日前=[1]
制約開始日六日前=[0]
制約開始日七日前=[]
制約開始日P1=[7]
制約開始日P2=[8]
制約開始日P3=[9]
制約開始日P4=[10]
制約開始日P5=[11]
制約開始日P6=[12]
第一週=[6,7,8,9,10,11,12]
第二週=[13,14,15,16,17,18,19]
第三週=[20,21,22,23,24,25,26]
第四週=[27,28,29,30,31,32,33]
第五週=[34,35]
第六週=[]
四週間=[6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33]
制約開始日1日前から=[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]
制約開始日2日前から=[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]
制約開始日3日前から=[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]
制約開始日4日前から=[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]
制約開始日5日前から=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]
制約開始日6日前から=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]
制約開始日7日前から=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]
制約終了日六日前=[29]
制約終了日五日前=[30]
制約終了日四日前=[31]
制約終了日三日前=[32]
制約終了日二日前=[33]
制約終了日一日前=[34]
金土日=[3,4,5,10,11,12,17,18,19,24,25,26,31,32,33]
金土日月=[3,4,5,6,10,11,12,13,17,18,19,20,24,25,26,27,31,32,33,34]
休日でない月=[6,13,20,27,34]
休日でない火=[0,14,21,28]
休日でない水=[1,8,15,22,29]
休日でない木=[2,9,16,23,30]
休日でない金=[3,10,17,24,31]
休日でない土=[4,11,25]
実月水=[1,6,8,13,15,20,22,27,29,34]
実月火水=[0,1,6,7,8,13,14,15,20,21,22,27,28,29,34,35]
実火土=[0,4,11,14,21,25,28]
実土ではない=[0,1,2,3,5,6,7,8,9,10,12,13,14,15,16,17,18,19,20,21,22,23,24,26,27,28,29,30,31,32,33,34,35]
実木金=[2,3,9,10,16,17,23,24,30,31]
Dr2人の木=[2,23,30]
Dr2人の金=[3,10,17]
今月月=[6,13,20,27,34]
今月火=[7,14,21,28,35]
今月水=[8,15,22,29]
今月木=[9,16,23,30]
今月金=[10,17,24,31]
今月土=[11,18,25,32]
実土=[4,11,18,25,32]
今月実土=[11,18,25,32]
実土でない=[0,1,2,3,5,6,7,8,9,10,12,13,14,15,16,17,19,20,21,22,23,24,26,27,28,29,30,31,33,34,35]
実土でない今月=[6,7,8,9,10,12,13,14,15,16,17,19,20,21,22,23,24,26,27,28,29,30,31,33,34,35]
土日=[4,11,18,19,25,32,33]
土日今月=[11,18,19,25,32,33]
休勤日=[5,12,19,26,33]
休勤日今月=[12,19,26,33]
#shiftcollection

#classcollection
全スタッフ属性=[全スタッフ]
雇用形態=[正社員,パート]

#shiftdef
shiftdef={'Work':Work}
#taskdef
taskdef={'日T':日T,'西拘束':西拘束,'拘束':拘束,'有給':有給,'公休':公休,'希望休み':希望休み,'NoTaskVar':NoTaskVar}
#non_auto_tasks
non_auto_tasks=['希望休み']
#phase_list
午前=0
午後=1
拘束=2

#task collections
休日集合={'有給':有給,'公休':公休,'希望休み':希望休み}
拘束集合={'拘束':拘束,'西拘束':西拘束}
働きカウント={'有給':有給,'日T':日T}
働きカウントしない={'公休':公休,'希望休み':希望休み}
#phase_objects def
phase_objects_def={'日勤','午前','午後','日勤拘束','日勤PV','公休PV','有休PV','希望PV','午前拘束PV','午前拘束西PV','午後拘束PV','午後拘束西PV','日勤拘束西PV','拘束ダメ','午前カウント','午後カウント','西の','拘束カウント','午前有休PV','午後有休PV','午前働','午後働','前後働'}
#phase_object_aggregates
phase_aggregate_object_def={'お休み','午前希望','午後希望','希望休み扱い','Any勤務','日勤扱い','午後扱い','有休集合','働集合'}
#digited group
勤務日数最大={0:22,1:22,2:14.5,3:14.5}
勤務日数最小={0:22,1:22,2:13,3:13.5}
有給日数最大={0:2,1:2,2:1,3:1}
有給日数最小={0:0,1:2,2:0,3:0}
週あたりの勤務日数最大={2:4,3:4}
週あたりの勤務日数最小={2:3,3:3}
補助制約={'拘束タスクは、午前なし':B_拘束タスクは午前なし,'拘束タスクは、午後なし':C_拘束タスクは午後なし,'日タスクは、拘束フェーズなし':D_日タスクは拘束フェーズなし,'西野先生ではない休日':西野先生ではない休日,'西野先生休日':西野先生休日,'西野先生平日拘束':西野先生平日拘束,'西野先生ではない日(ひ)':E_西野先生ではない日ひ,'西野先生土曜日':西野先生土曜日}
column_constraints={'列制約グループ1':列制約グループ1,'補助制約':補助制約}
#shift schedules
shift_schedules=[[('',0),('日勤',0),('日勤拘束',0),('日勤',0),('お休み',0),('お休み',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('希望休み扱い',0),('',0),('',0),('',0),('',0),('',0)]
,[('',0),('日勤拘束',0),('日勤',0),('お休み',0),('午前',0),('日勤拘束',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('拘束ダメ',0),('希望休み扱い',0),('希望休み扱い',0),('',0),('',0)]
,[('',0),('お休み',0),('有休PV',0),('午前',0),('午前拘束PV',0),('お休み',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('拘束ダメ',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0)]
,[('',0),('日勤',0),('午前',0),('日勤拘束',0),('お休み',0),('お休み',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('午前希望',0),('',0),('希望休み扱い',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('希望休み扱い',0),('希望休み扱い',0),('',0),('',0)]
]
task_schedules=[[('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0)]
,[('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0)]
,[('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0)]
,[('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0),('',0)]
]
project_file_path='C:/Users/sugaw/Documents/FA/sc3'
shift_solution=[['Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work']
,['Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work']
,['Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work']
,['Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work','Work']
]
task_solution=[['公休','日T','公休','日T','日T','公休','日T','日T','拘束','日T','日T','公休','公休','公休','公休','公休','公休','公休','日T','日T','拘束','日T','日T','公休','日T','日T','公休','日T','日T','拘束','公休','公休','公休','日T','公休','公休','日T','日T','拘束','公休','公休','公休','日T','日T','公休','日T','日T','公休','日T','日T','拘束','日T','日T','公休','公休','公休','公休','公休','公休','公休','日T','日T','拘束','日T','日T','公休','日T','日T','公休','有給','有給','公休','日T','日T','拘束','日T','公休','公休','公休','公休','公休','日T','日T','公休','日T','日T','拘束','日T','日T','公休','有給','有給','公休','日T','公休','公休','日T','公休','西拘束','公休','公休','公休','日T','日T','公休','日T','日T','公休']
,['公休','日T','公休','日T','日T','拘束','日T','日T','公休','公休','公休','公休','日T','公休','公休','日T','日T','拘束','公休','公休','公休','有給','有給','公休','日T','日T','公休','日T','日T','公休','日T','日T','公休','日T','公休','拘束','公休','公休','公休','日T','日T','公休','日T','日T','公休','日T','日T','拘束','日T','日T','公休','有給','有給','公休','日T','公休','公休','日T','日T','西拘束','公休','公休','公休','日T','日T','公休','日T','日T','拘束','日T','日T','公休','日T','日T','公休','公休','公休','公休','日T','日T','拘束','公休','公休','公休','日T','日T','公休','日T','日T','公休','日T','日T','拘束','日T','日T','公休','公休','公休','公休','公休','公休','公休','日T','日T','公休','日T','日T','拘束']
,['日T','公休','公休','公休','公休','公休','有給','有給','公休','日T','公休','公休','日T','公休','拘束','公休','公休','公休','日T','日T','公休','日T','日T','公休','日T','日T','拘束','公休','公休','公休','日T','公休','公休','公休','公休','公休','公休','公休','公休','日T','日T','拘束','公休','公休','公休','日T','日T','公休','公休','公休','公休','日T','日T','拘束','公休','公休','公休','公休','公休','公休','日T','日T','公休','公休','日T','拘束','有給','日T','公休','日T','公休','公休','公休','公休','公休','日T','公休','拘束','公休','公休','公休','日T','日T','公休','公休','公休','公休','日T','日T','拘束','日T','公休','公休','公休','公休','公休','日T','公休','公休','日T','日T','西拘束','公休','公休','公休','公休','日T','公休']
,['日T','日T','公休','日T','日T','公休','日T','公休','公休','日T','日T','拘束','公休','公休','公休','公休','公休','公休','日T','日T','公休','日T','日T','拘束','公休','公休','公休','公休','公休','公休','日T','日T','拘束','公休','公休','公休','公休','公休','公休','日T','日T','公休','有給','日T','拘束','公休','公休','公休','公休','公休','公休','日T','公休','公休','日T','公休','西拘束','公休','公休','公休','日T','日T','公休','有給','公休','公休','日T','公休','公休','日T','日T','拘束','公休','公休','公休','公休','公休','公休','公休','公休','公休','日T','日T','拘束','公休','日T','公休','公休','公休','公休','日T','日T','公休','公休','日T','拘束','公休','公休','公休','公休','公休','公休','日T','日T','拘束','日T','公休','公休']
]
import sc3
import sys
import os
import csv

def 勤務カウントOneShift(person,day,vlist):
            v=sc3.GetTaskVar(person,day,0,'日T');#AM
            vlist.append(v)
            v=sc3.GetTaskVar(person,day,1,'日T');#PM
            vlist.append(v)
            v=sc3.GetTaskVar(person,day,0,'有給');#AM
            vlist.append(v)
            v=sc3.GetTaskVar(person,day,1,'有給');#PM
            vlist.append(v)



def 勤務日数設定OneShift():
    for person in 全スタッフ:
        max=-1
        min=0

        st=staffdef[person]+' 勤務日数を制約'
        sc3.print(st+'します。\n')
        if person in 勤務日数最大:
                max=勤務日数最大[person]#float
        if person in 勤務日数最小:
                min=勤務日数最小[person]#float
        
                    

        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 今月:#午前・午後の日勤と有給taskと有給シフトをカウントする 有給シフトは、2回分
            勤務カウントOneShift(person,day,vlist)
        #sc3.AddSoft(sc3.SeqError(min,max,15,vlist),st,7)#許容2 ->8hours
        sc3.AddHard(sc3.SeqLE(min,max,vlist),st)#hard constraint
def 有給日数設定OneShift():
    min_sum=0;
    vlist_sum=[];
    for person in 全スタッフ:
        max=-1
        min=0

        st=staffdef[person]+' 有給日数を制約'
        sc3.print(st+'します。\n')
        if person in 有給日数最大:
                max=有給日数最大[person]#float
        if person in 有給日数最小:
                min=有給日数最小[person]#float
        
                    

        max *=2
        min *=2
        min_sum+=min;
        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 今月:#午前・午後の日勤と有給taskと有給シフトをカウントする 有給シフトは、2回分
            v=sc3.GetTaskVar(person,day,0,'有給');#AM
            vlist.append(v)
            v=sc3.GetTaskVar(person,day,1,'有給');#PM
            vlist.append(v)

        sc3.AddSoft(sc3.SeqError(min,max,1,vlist),st,7)
        vlist_sum+=vlist;
    sc3.AddSoft(sc3.SeqError(min_sum,min_sum,10,vlist_sum),'Total有給の極小化',5)#重みは調整ください

def 週あたりの勤務日数設定OneShift():
    for person in 全スタッフ:
        max=14
        min=0

        st=staffdef[person]+' 週あたりの勤務日数を制約'
        sc3.print(st+'します。\n')
        if person in 週あたりの勤務日数最大:
                max=週あたりの勤務日数最大[person]#float
        if person in 週あたりの勤務日数最小:
                min=週あたりの勤務日数最小[person]#float
        if max==14 and min==0:
            continue

                    

        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')

        week=0        
        for sun in 日:#日曜日を起点に1週間見る
            sc3.print('日曜日='+str(sun)+'\n')
            VList=[]
            days=0
            for d in range(7):
                day=sun+d
                
                if day >制約終了日:
                    break
                else:
                    勤務カウントOneShift(person,day,VList)
                days+=1

            s="週あたりの勤務回数設定"+str(week)+' '+"person"+str(person) #Keyword+ space +variable word
            week+=1
            if days==7:
                sc3.AddSoft(sc3.SeqError(min,max,2,VList),s,7) #level 3 許容エラー4
            else:
                nmax=int( days*max/7);#7日に満たない場合は、リニア目標値設定 端数切捨て
                nmin=int( days*min/7);#7日に満たない場合は、リニア目標値設定 端数切捨て
                sc3.print(str(nmax)+"に目標値最大を設定しました。\n")
                sc3.print(str(nmin)+"に目標値最最小を設定しました。\n")
                sc3.AddSoft(sc3.SeqError(nmin,nmax,2,VList),s,7)#level 3 許容エラー4 

#post operation
def get_day_index(day):
    return day-今月[0]+1 #名前項目分
    
def draw_row(person,day,row0,row1):
    phases=3
    t0=task_solution[person][day*phases+0]
    t1=task_solution[person][day*phases+1]
    t2=task_solution[person][day*phases+2]
    d=get_day_index(day)

    #debug sc3.print(str(d)+' '+str(len(row0))+'\n')

#勤務処理
    if t0=='日T' and t1=='日T':
        if day in 休勤日今月:
            row0[d]='休勤'
        else :
            row0[d]='日勤'
    elif t0=='日T' and t1=='有給':
        row0[d]='後有'
    elif t1=='日T' and t0=='有給':
        row0[d]='前有'
    elif t0=='有給' and t1=='有給':
        row0[d]='有休'
    elif t0=='公休' and t1=='公休':
        row0[d]='休み'
    elif t0=='日T' and t1=='公休':
        row0[d]='午前'
    elif t1=='日T' and t0=='公休':
        row0[d]='午後'
    elif t0=='希望休み' and t1=='希望休み':
        row0[d]='希休'
    else:
        row0[d]='M休'
    

#属性処理
    if t2 !='公休':
        if day in 土:
            row1[d]='土拘'
        else :
            row1[d]='拘束'
        if shift_schedules[person][day]=='希望休み扱い':
            if row0[d] !='希休':
                row1[d]='希休'



def post_main():
    sc3.print('\n\n*********ポスト処理を実行中です。*************\n')
    sol_list=[]
    for person in 全スタッフ:
        row0=[]
        row1=[]
        row0.append(staffdef[person])
        row1.append('')
        for day in 今月:
            row0.append('') #配列確保
            row1.append('') #配列確保
            draw_row(person,day,row0,row1)
        sol_list.append(row0)
        sol_list.append(row1)
#Write csv file
    sc3.print('CSVファイルを生成中です。\n')
    os.chdir(project_file_path)
    file_path=project_file_path+'/this_month_solution.csv'
    with open(file_path, 'w', newline='') as file_path:
        writer = csv.writer(file_path)
        writer.writerows(sol_list)
    sc3.print('CSVファイルを生成しました。\n')
    sc3.print('********ポスト処理を終了します。*******************\n')

#Main rountine

post_main()

プリ・ポスト共GUI集合情報を元に記述できるので便利です。大半の情報は、既にGUIで記述済みですので、記述量が少なくて済みます。

実装では、pythonのソース解析が必要かなと一瞬思いましたが、Pythonの単純な文法構造のおかげで、Bisonを用いての真面目な処理は、行っていません。

以上の実装後の、ログは次のようになりました。
コンパイルの準備中ソルバを呼び出し中です。
 Python ファイルを生成中です。
 Python プロパティファイルの生成が終わりました。
 制約をコンパイル中です。
スタッフ1 勤務日数を制約します。
最大=44 最小=44
スタッフ2 勤務日数を制約します。
最大=44 最小=44
スタッフ3 勤務日数を制約します。
最大=29 最小=26
スタッフ4 勤務日数を制約します。
最大=29 最小=27
スタッフ1 有給日数を制約します。
最大=4 最小=0
スタッフ2 有給日数を制約します。
最大=4 最小=4
スタッフ3 有給日数を制約します。
最大=2 最小=0
スタッフ4 有給日数を制約します。
最大=2 最小=0
スタッフ1 週あたりの勤務日数を制約します。
スタッフ2 週あたりの勤務日数を制約します。
スタッフ3 週あたりの勤務日数を制約します。
最大=8 最小=6
日曜日=5
日曜日=12
日曜日=19
日曜日=26
日曜日=33
3に目標値最大を設定しました。
2に目標値最最小を設定しました。
スタッフ4 週あたりの勤務日数を制約します。
最大=8 最小=6
日曜日=5
日曜日=12
日曜日=19
日曜日=26
日曜日=33
3に目標値最大を設定しました。
2に目標値最最小を設定しました。
 Algorithm 1 Solving Process Started..
   o 9920   0.964000(sec) 
   o 8930   0.970000(sec) 
   o 6780   0.987000(sec) 
   o 5770   0.999000(sec) 
   o 2780   1.010000(sec) 
   o 1780   1.018000(sec) 
   o 630   1.085000(sec) 
   o 580   3.105000(sec) 
   o 530   3.157000(sec) 
   o 520   4.958000(sec) 
 充足解を書き込みました。
 139612[KB] used.
 5.729000(sec)
 Python ファイルを生成中です。
 Python プロパティファイルの生成が終わりました。
 _____________________________________
|           |           |             |
|   Weight  |   Errors  |    Cost     |
|___________|___________|_____________|
|           |           |             |
|      1000 |         0 |           0 |
|       100 |         0 |           0 |
|        50 |         9 |         450 |
|        10 |         7 |          70 |
|___________|___________|_____________|
|                       |             |
|         Total         |         520 |
|_______________________|_____________|
o 520(0)


*********ポスト処理を実行中です。*************
CSVファイルを生成中です。
CSVファイルを生成しました。
********ポスト処理を終了します。*******************
解探索が終了しました。 7 (秒)
解が得られました。



良くあるエラーは、次です、<string>(434)ダブルクリックして次の表示となります。アクセス拒否されたので、Excelを終了させて再試行してください。


2020年6月28日日曜日

Python Editor 検索・置換の実装

ソースが長くなってくると、やはりあった方がよいので実装しました。
検索置換をクリックするとダイアログが出てきます。ソースエディタは、3インスタンス(ソース、ソース全体、ソース全体(ポスト)あるのですが、それぞれに固有のダイアログになります。

2020年6月27日土曜日

フェーズオブジェクトの時間カウント

列行の時間制約及び、行の整数計数で、使用可能です。
 
使用にあたっては、制限があります。
それは、排他的オブジェクトの集合であるということです。
シフト集合の時間和については、何らこの要件は、ありませんでした。なぜなら、シフト集合は、無条件に排他的オブジェクト集合になるからです。
例えば、
深準=深夜 |準夜
というシフト集合を考えたとき、深夜または、準夜勤となり、どちら一方がアサートされます。
両方同時に成立することはあり得ません。(一日内でのシフトは、唯一一つだけアサートされる、という原則の元で定義されています。)一方が成立したとき一方は成立しないこうした集合を排他的といういいます。ANDを取ると空集合となるのが特徴です。排他集合のとき、
成立するのは常に一つなので、一日の加算和時間は、
加算和=深夜*深夜の時間+準夜勤*準夜勤の時間+...
という式で書くことができます。 
シフトのときは、何もしなくてもこれが成立していたので特に意識する必要はありません。
しかし、ユーザが書くフェーズオブジェクトが、排他的となる保証はありません。時間制約もしくは、整数計数を行う場合にはこれを意識する必要があります。
 
 
排他的オブジェクトの要件は、フルデコードして、少なくとも一つのフェーズで違うことです。
例えば、
午前働
午後働
前後働
の集合である働き集合は、3つのフェーズオブジェクトを持っています。この3つから2つを取る全ての組み合わせで、違う必要があります。実際、3C2=3通りの組み合わせ全てで違うので問題ない、ということが言えます。
 
排他的オブジェクトではない例、
午前カウント
午後カウント
の集合を考えましょう。ブランクは全てにマッチすると考えるので、排他的ではありません。
 
中々難しいですが、論よりランしても問題ありません。排他的オブジェクトかどうかは、次のC++アルゴリズムでチェックしており、違反する場合はエラーとなりコンパイル出来ません。なお、next_combinationについては、こちら
 
 
 
bool phase_object_aggregate::do_exclusive_check(constraints& c)
{
 vector>> input;

 make_bitset_vec(c, input);

 vector ca, cb;

 for (size_t i = 0;i < input.size();i++) {
  ca.push_back(i);
 }

 cb.push_back(0);
 cb.push_back(1);

 do
 {//全ての2項組み合わせをチェックする
  vector ::iterator im;
  vector>> temp_list;

  for (im = cb.begin();im != cb.end();im++) {
   int j = (*im);
   temp_list.push_back(input[j]);
  }
  assert(temp_list.size() == 2);
  assert(temp_list[0].size() == temp_list[1].size());
  bool success = false;
  for (auto m = 0;m < temp_list[0].size();m++) {
   if (temp_list[0][m]!=0 && temp_list[1][m]!=0) {//マッチチェックは両方0でないときだけ
    bitset b = temp_list[0][m] & temp_list[1][m];//排他性のチェック ANDを取って空集合でなければアウト
    if (b == 0) {
     success=true;//一つのPhaseでもマッチしなかったらOK
    }
   }
  }
  if (!success) {//全Phaseで空集合でない →アウト
   return false;
  }
 } while (stdcomb::next_combination(ca.begin(), ca.end(), cb.begin(), cb.end()));
 set_exclusive_check_done();
 return true;//全組み合わせでOKのときだけOK
}

2020年6月26日金曜日

パターン最後の曜日タイプ

最初の曜日タイプは、ありますが、最後の曜日タイプは、今までありませんでした。
つまり、最初の日基準で記述する必要がありました。

次期SC3では、行パターン禁止に、最後の曜日タイプを追加しました。これを用いると最後の日基準で制約を書けます。対象は、シフト及びフェーズオブジェクトです。最後の曜日が、最後の曜日にマッチする場合に処理が進みます。マッチしない場合は、何も起こりません(禁止動作は起こりません)

<最初と最後の両方で記載がある場合>
問題ありません。両方とも各々マッチチェックが行われます。ですので、大抵の場合、何も起きません。(禁止動作は起こりません) 両方マッチした場合だけ禁止動作が起きます。


2020年6月25日木曜日

フェーズオブジェクトの予定制約

フェーズオブジェクトは、予定制約として入力することも出来ます。シフトの代わりとして入力するイメージです。タスク入力で入力することも出来ますが、一日単位での入力の方が楽です。
下は、ユーザプロジェクトでシフトベースで書かれたものです。上のプロジェクトは、それをフェーズオブジェクトベースで書き直したものです。ほぼ、同じように入力とすることも可能ですし、
さらに高精細な入力とすることも出来ます。勿論ソフト制約も可能です。



2020年6月24日水曜日

フェーズオブジェクトのカウント

フェーズオブジェクト用に「整数計数」を追加しました。


一番上の行制約は、通常の基数制約ですが、同じフェーズオブジェクトでも整数計数とすると
フェーズオブジェクトのカウント値を計数します。
タスク集合の定義は、
■働きカウント:日タスクまたは、有給タスク
■働きカウントしない:公休タスクまたは、希望休みタスク

 としています。



フェーズ変数の定義は、

整数計数で使われるのは、カウント値です。午前もしくは午後のみ働く(有給含む)カウント値は1としています。午前午後両方働くカウント値は、2です。

フェース変数集合の定義は、下の通りです。
計数しているのは、働集合 で、
働集合=午前働(1) |  午後働(1) | 前後働(2)
です。それぞれ、同時には成立しない関係であることに注意してください。例えば、午前働と前後働きは、同時には成立しません。(これが整数計数のための要件です。シフトでは、自動的にこの要件は満足しますが、フェーズ変数集合では、ユーザ定義に依存します。)
こうすると、前後働のときは、2がカウントされ、午前働時には1がカウントされます。

このケースの場合時間指定での制約は、176時間でした。8時間カウントでは、22日間になりますが、午前、午後を1として、等価な制約は、44になります。(4時間をひとコマとして、44Unitという意味になります。)これは、時間制約ではないので、ソフト制約化することも可能です。

フェーズ変数も定義することなくPythonで造作なく書けますが、なるべくPythonを使わないで書けることも多くの方に使って頂ける要件かな?と思いました。








2020年6月23日火曜日

フェーズ変数とフェーズ変数集合

新しい概念です。
フェーズ変数の定義例です。
この例では、フェーズは、午前・午後・拘束の3つのフェーズになります。
各フェーズに対してタスク変数を呼んでフェーズ変数を定義している様子が下です。タスク集合を呼ぶことも出来ます。

今までの公休は、午前・午後共に公休でありこれを公休PVとしています。
今までの有休は、午前・午後共に有給でありこれを有休PVとしています。

休み集合は、公休PV、または、有休PV(OR集合)となります。


フェーズ変数は、基本的には、Day幅(3Phase)で定義します。こうすることで、今までのシフトと同じ扱いとすることが出来ます。下のように行制約として呼ぶことが出来ます。
パレットは、フェーズ変数・フェーズ変数集合になっています。シフトと混在させたパターンとすることも可能ですが、本例の場合1シフト(W)だけなので、意味はないです。
 
 
 
 

2020年6月21日日曜日

PHS待機問題の対応構想

なるべくPythonでの記述をなくして、GUIだけで記述できないかを検討しました。

新しく、PhaseVarというオブジェクトを作れば、対応できそうだ、という結論に至りました。

PhaseVarというのは、PhaseとTask・Task集合を結びつけるオブジェクトになります。




PhaseVar
PhaseVar名ラベルPhase0(午前)Phase1(午後)Phase2(拘束)
日勤   
午前    
午後   
拘束日勤  拘束集合
午後拘束  拘束集合
午前拘束西   西

 さらにPhaseVarの集合を定義すればより汎用的でしょう。
(また、PhaseVarは、OneDayに限定されることはない、翌日に跨ってもよさそうです。)

例えば、日勤は、午前午後共 働くという意味の変数になります。午前という変数は、Phase0上に日(Task)があったときにアサートされる変数です。日勤拘束という変数は、日勤の上にさらに拘束集合(TaskAggregate)がアサートされる変数です。このように、シフトベースで作っていたシフトラベル名が、フェーズで改めて明確に定義されるという風にも理解できます。

こうしたオブジェクト郡を定義して行制約で使えるようにします。この構想にすると、設計構造上、大変更が必要になってしまいます。今までTask周りは、殆どPythonのお世話にならずには書けない事態に陥っていましたが、逆に殆どPythonを使わずに書ける可能性が出てきました。また、Python制約では、基数制約については、GUI表示を実現していますが、その他については、計数表示できない、複数解に対応していない等、色々制限があります。これら諸問題も同時にクリアできます。

これらの実装には、一ヶ月程度かかると思います。AWS対応以上に変更インパクトがあります。SC3正式リリース、及びAWS DOCKER対応は、上記問題の解決後としておよそ6週間遅延します。

大変大きなインパクトではありますが、Pythonを500行書くよりは、GUI数十行で済ませたい、実際に書いてみて、切にそう思いました。





2020年6月20日土曜日

PHS待機・拘束 問題

訪問看護や、在宅診療を行っているところで、よく出てくる問題です。スタッフ数は、数人ですし、2交代や、3交代という範疇にもあてはまりません。にも関わらず難しい問題です。シフトベースで書こうとすると、

A) 午前のみ
B) 午後のみ
C) 午後+拘束
D) 午前+有休4(実際は午後休み)
E) 有休4+午後(実際は午前休み)
F) 有休4+午後+拘束(午前休み)
G) 有休8(1日休み)
H) 公休4+有休4(実際は一日休み)
I) 日勤
J) 公休

となり、特に半日単位での仕事があるとシフトラベル数が半端ではなくなってしまいます。
さらに、公休+有休があったりするとシフトと時間では、記述するのが難しくなってきます。

それならばと、半日単位でフェーズをつくりました。以下の1シフト、3フェーズです。
 
午前 | 午後 | 拘束

拘束フェーズは、常に1人確保しています。
拘束というのは、夜間での自宅待機のことです。別な医療機関では、PHS待機という言い方をされていました。夜勤ではないけれども、いつ呼び出されるか分からない、ということのようです。(高齢化社会での医療費抑制で、病院から在宅へという流れが底流にあります。) 拘束フェーズは、シフトとは関係なく存在するので、本来シフトとは分けるべきものです。しかしながら、シフトは、その特性上一日で存在するシフトは、唯一一つのみ、が原則です。こういった事情で、シフトでは組みにくいのです。

午前・午後とは独立して書けます。それぞれの午前・午後フェーズには、日、公、有しか入りません。拘束フェーズには、拘束のみ(公休は、Disabedの意味で使っています)となります。結果、
一見してシンプルになります。

しかし、ここまでで、Pythonが500行近くとなり、私でもかなりの困難がありました。
この書いたPythonコードを呼べば、どのような制約でも書けるのですが、GUIの手軽さがなく、上級者でないと使いこなせない、という問題があります。

つまり、シフトベースで書けない→GUIでは、殆ど書けなくなってしまう問題です。
これは、現在も悩み中です。

多分、日のあたらない分野ではあります。ユーザが書いた制約を見ると、シフトと時間ベースで
書かれているのですが、大変に精緻、かつ医療の品質とスタッフの負荷軽減の平準化制約が凄くて、驚きました。しかし、こうした事務方の支えがあって、日本の在宅医療は支えられているのではないかと思いました。

私事ですが、
私の義母も震災後から自宅で寝たきりとなり昨年亡くなりました。その間、在宅介護、訪問看護、訪問診療を受けていました。もしそれらのサービスがなかったとしたら、家庭崩壊は免れなかったと思います。米国の高額医療費問題を見るにつけ、日本の制度設計の有難さを感じます。






2020年6月19日金曜日

翻訳が上がってきました

GENGOに頼んでいた翻訳が上がってきました。PowerPoint原稿が英語になって戻ってきました。
アプリ分野で選択したら、上級者しか選択出来ませんでした。アップロードしたPowerPointのテキスト部を自動計算されます。1文字9円で、8000文字弱で自動的に注文前に、代価が決定されます。トランスレータへは簡単な指示を書き、支払いして注文完了です。およそ4日で仕上がり、承認して終了となりました。感想は、自分がやるよりは、100倍良いです。が、それでも専門用語関係ではいくつか?はあります。専門用語とは気付かずに渡した結果で、致し方ないです。(例えば、深準型、正循環などは、そもそもどう訳せばよいでしょうか?日本語でも分かる人だけに分かります。) 

ドキュメントは、4つありますが、3つまでは同様に、頼もうかと思います。(4つめのPythonは、さすがにソフトウェアエンジニア向けなので。)

参考までに、日本語アプリに英語というありえない翻訳後の生Tutorialをお楽しみください。

2020年6月18日木曜日

サイトの構築

英語用サイトを作ろうと思うのですが、さすがに現代風にしないと、と思っていました。(今のサイトは石器時代です。)

条件としては、Wordpressでないこと、モバイル対応が自動でなされることです。

NotePad++みたいなサイトがいいなと思っていたのでソースを見るとHugoで書いてあることが分かりました。Hugo よさそうです。

2020年6月17日水曜日

シフト勤務表の標準パターン

次の3パターンに分類されると思います。2交代は、さらに、遅番、早番、超遅番,,等が入いる場合がありますが、夜勤の基本パターンは同じです。

■2交代
■3交代正循環
■3交代深夜準

これらは、いずれも、1日に1個のシフトが決まる形です。
これについて、次回リリースより標準パターンを添付します。ご覧のように2交代については、明け後2連休、3交代については、完全正循環、完全深準を実現し,理想的な平準化が行われています。(多分どちらも、人間で作った人はいないでしょう。)実は、正循環と深準は、パターンだけが違い、その他は同じです。ですので、同じ予定で評価できました。やはり正循環の方が難しい、私的に言うと、解空間が狭かった、ということは、言えます。








後で、それらのパターンの使い方等の解説をしますので、ご活用ください。


これとは、別に半日単位での職場があります。4直3交代等の工場勤務です。また、PHS待機や、拘束夜勤待機等、診療所・訪問看護・在宅診療では、半日もしくは、1/3日単位で記述した方がすっきりする場合があります。こちらは、中々標準化ということは難しいです。一つのアイデアとしては、上のように標準パターンプロジェクトを作っておいて、読み込んで、必要な改修箇所をGeneratorというものです。Generatorは、pythonも自動生成する必要があります。アイデアはあるのですが、開発リソースがありません..



2020年6月16日火曜日

PowerPointをしおり付きPDFにしてHtml化する

PowerPointからPDFにしました。その際、bookmarks(しおり)が出せないことを知りました。AcrobatDCが必要となるようです。

<しおり作り>
Textファイルで作ったしおりを、jpdfbookmarks というソフトを使って、Importしました。テキストファイルは、以下です。

ドキュメント構成/2,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
ソフトの起動/4,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
プロジェクトファイルの読み込み/6,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
解画面の読み込み/9,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
列制約/13,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
求解/15,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
列制約グループのオンオフ/17,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
制約がかかっていないと/20,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
個別制約のオンオフ/21,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
スタッフ名変更/22,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
シフト追加/23,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
基数制約/24,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
グループタイプを定義/28,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
曜日設定/30,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
行制約/35,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
2交代パターン/36,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
夜勤数公休数/37,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
チュートリアル6エラー/39,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
エラー まとめ/43,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
ソフト制約の意味/46,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
予定制約のソフト化/51,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
予定入力との比較/52,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
最適化まとめ/53,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
ペア制約/55,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
プロジェクトの保存/56,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
翌月への移行/57,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
まとめ/64,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0
用語集/65,Black,notBold,notItalic,open,TopLeftZoom,0,0,0.0


これで、しおり付のPDFが出来ました。その後、PDF2HtmlExで PDFをHtml化すると、しおり付HTMLが出来あがります。
C:\Users\tak.sugawara\Downloads\pdf2htmlEX-0.12>pdf2htmlEX --dest-dir out7 sched
ule_nurse3_tutorial.pdf
Preprocessing: 66/66
Working: 66/66

AcrobatDCがないとかなり面倒ですが、一応出来ました。
こちらのJavascriptを使用させて頂いています。出来上がりはこちらです。

PDF Exchange等を使えば、しおりを作る事はできますが、メンテナンスを考えるとしおり自体を保存できるようにしたい、というのがjpdfbookmarks使用の背景です。こんな事をしなくてもPowerPointがやってくれれば、それで済むのですが。

2020年6月15日月曜日

マニュアル翻訳検討

PowerPointにアドオンで翻訳機能を追加してみましたが、全く使えませんでした。

なので、プロの翻訳者を頼むことにしました。PowerPoint原稿をそのまま訳してくれるところに頼もうと思います。その結果をアプリにも組み込みます。

2020年6月13日土曜日

探索プログラムの改善その2

エリート集団だけでは、十分でないことが分かりました。一見、優秀な人だけ集めても、優秀さが長続きする保証はない、一見劣るけれども長期で見ると成果を出す輩がいる、そんな感じでしょうか。 例えば、インスタンス7でみると、BFSでSCANして行って、Integer解は、depth=9で見つかりました。そのとき、最もBottomを探索しているnodeのdepthは、16でした。両者ともobj=1056です。
インスタンス8で見ると、Bottomがdepth47のとき、Integer解は、depth=17で見つかりました。obj=1299.408です。これから、エリートが、必ずしも速くInteger解を生むという関係ではないことが分かります。

特に、線形緩和解のLBがInteger解のLBと離れているときには、この戦略は有効ではないことが分かりました。そこで、ROOTから多様な解を維持する戦略としました。勿論、全てを取ると組み合わせ爆発してしまうので、そこは代表選手を取ります。この戦略によれば、一定以上の確率でOptimum解が見つかるはずです。

2020年6月12日金曜日

PDF to HTML

マニュアルを書いていて、やはりHTMLでも出したくなります。

なかなか良いものがないのですが、http://pdf-file.nnn2.com/?p=883 が良さそうです。ただし、
メンテナが不在のようです。https://github.com/coolwanglu/pdf2htmlEX

 

2020年6月11日木曜日

SC3用マニュアル作成中

リリースに向けマニュアル作成中です。以下の4ドキュメントに分けます。目新しいものはありません。少しだけ修正が入っていますが、基本今までの焼き直しです。が、全体分量が300ページ程度なので、思ったよりも時間がかかっています。これの英語版を作るとなると、気が滅入ります。

■チュートリアル
■ユーザマニュアル
■アドバンストユーザマニュアル
■Python制約プログラミングマニュアル

2020年6月10日水曜日

平日連休

土日連休最低1回は、看護協会仕様ですが、平日連休というのは、初めてみました。

実装ですが、GUIのみで可能です。
MyU祝は、年間カレンダ上の定義です。ご覧の通り、何も定義していないので、何も作用しないのですが、カレンダ祝以外に、祝扱いにしたい日があった場合には、ここに書き込めば、自動的に祝扱いにすることが出来ます。(さらに汎用的にするには、もう一つ追加して、カレンダ祝を祝扱いにしたくない、というMyU非祝を作っておけば、完璧です。要求がなくても入れ込んでおけば、プロ認定です。)

My祝は、祝または、MyU祝と定義しています。以下同様で、土日連休と同じように定義すればOKです。





2020年6月9日火曜日

祝日のシフト

カレンダー上の祝日休みは、公休をつけるのではなく祝日(シフト)としたい。

というご要望がありました。祝日は、公休ではなく、次のような場合連続勤務とカウントするようにしたいとのことです。


例①
7月21日 入り
7月22日 明け
7月23日 祝(日勤扱い)
7月24日 祝(日勤扱い)
7月25日 公休
となり、もし20日が日勤であれば19日は必ず休みになります。(20日から24日で5連勤
となるため)

例②
7月22日 入り
7月23日 明け
7月24日 祝(日勤扱い)
7月25日 公休
ここで、日勤扱いとしていますが、ユーザの真の要求は、公休でない勤務が連続6日あってはいけないというこです。(惑わされてはいけません。祝を日勤集合のなかに入れてしまうと、列制約もおかしくなってしまいます。) ✓公休 の意味は、公休でない=公休以外のシフト になります。
これを使うことによって、一々集合定義する手間が省けます。

要は、今までの

A)明け →公休

という制約を
A’)明け →公休 または 祝

という制約にすればよいことが分かります。ですので祝というシフトを追加することにします。
また「公祝」という公休または祝 集合シフトを作ります。

次に祝というシフトの要件を考えます。
B)祝日 :祝が存在できる  公休は、存在してはならない
C)祝日以外:祝は存在してはならない 公休は、存在できる


従って、AをA'に変更、B,Cを記述追加をすればよいことになります。

制約は、以上ですが、祝日というは曲者で、カレンダ上の祝日が必ずしもその病院での祝日ではないというケースも想定されます。そこで、ユーザ祝日を年間で定義しておくとよいでしょう。




2020年6月7日日曜日

探索プログラム改善

どうも思うように解が出てこないので、さらに一工夫することにしました。前回、探査木のROOT付近では、BFSある程度depthが深くなったところでDFSにする話をしました。切り替え境界のdepthが深ければ深いほど、よりOptimumに近づくと考えられます。理想を言えば、BFS中にInteger解が得られたらそれがBestです。なので、同じ時間内で、もっとdepthを掘ることを考えます。そのためには、同じdepth中のtop Kだけを取ります。言い換えると、その時点の質のよいエリートだけを対象とします。その時点で出来がよくないのは、将来(ボトム方向に下降したときに)劇的改善は見込めないだろう、亀は兎を追い越せないだろう、という発想です。

こうすることで、厳密LBではなくなってしまいます。推定LBとなってしまう代わりに、下位depthにおいて、OptimumなObj付近を効率よく探索できるということです。Shiftsが7だと、全組み合わせは、7**depth となり高々depth30程度でも1e25程度の空間となります。Instance15で、Interger解が得られているのは、現在のところdepth 140付近です。

2020年6月6日土曜日

MaxSAT2020案内が届きました。

今年は、SAT conferenceがオンライン開催になったので、MAXSATもそうだと思います。
今年は、新しいカテゴリとして、TOP Kというのが創設されました。元々、OR系であったので、
その類型だと思います。

Dear all,
The MaxSAT Evaluation 2020 is coming up soon!
We have extended the deadline for submitting benchmarks and solvers
for the MaxSAT Evaluation 2020 to Friday, June 12, 2020.
The final version of your solver must be uploaded to the following
subspaces (depending on the category you want to participate):
- MSE2020->Evaluation->Complete->Unweighted
- MSE2020->Evaluation->Complete->Weighted
- MSE2020->Evaluation->Incomplete->Unweighted
- MSE2020->Evaluation->Incomplete->Weighted
- MSE2020->Evaluation->TopK->Unweighted
- MSE2020->Evaluation->TopK->Weighted
More details on the final submission are available at:
https://maxsat-evaluations.github.io/2020/submission.html
We have a new track this year to enumerate the top-k solutions. We
encourage you to submit to this track if you can change your current
algorithm to support the enumeration of solutions. More information
about this track is available at:
https://maxsat-evaluations.github.io/2020/topk.html
So far we did not receive many new benchmarks. If you are using MaxSAT
to solve problems, we encourage you to submit your formulas to this
year's MaxSAT Evaluation. New benchmarks are critical for the
continuous improvement of MaxSAT solvers and to perform a better
assessment of their performance.
Best regards,
The MaxSAT 2020 organizers

2020年6月4日木曜日

新解探索プログラム

SchedulingBenchmarkサイトで、解の更新がなされ、手持ちの新解は、全て発見されてしまいました。可能性が残っているのは、Instance15のみです。そこで、新解を発見できなくてもLBの更新を目指すことにしました。現在LBは、3823がKnown Best値となっており、3827が現在、私が持っている結果です。この時点で報告してもよいのですが、残念ながらEvidenceがありません。

計算機を廻すのは、一週間以上を想定します。次のStarategyでLBの更新を狙います。

1)DFS→ルート周りはBFSに変更


これで、少なくともLBが更新できると思います。今までは、DFS一辺倒だったのですが、LBを更新するとなると、全ての探索木で、LB以上となる証明が必要となります。そのためには、DFSではなく、BFSが必須です。しかし、全部をBFSにすると爆発して一生、Feasibleな解は見つかりません。なので、ある程度以上のdepthでは、DFSとします。これで、LBの更新と、それまでのBest解の両方を作戦です。一度限りのプログラムなので、Ifdefで作成しました。


2)部分的に問題そのものを緩和
問題を簡単にすれば、自動的にLBも下降します。しかし、そのLBがKnownBest以上である限りにおいては、LBを求める意味はあります。問題が緩和されたLBは、緩和される前のLBよりも確実に小さくなるはずです。つまり問題が緩和されたLBは、元の問題のLBとして担保できます。
しかし、注意しなければならないのは、得られた解が、元の問題上でのFeasibleな解には必ずしもならない、ということです。こうまでして緩和を行うのは、探索速度の向上が期待できるからですが、これは、問題構造を熟知しているからできることであって、汎用手段ではありません。なりふり構わず新LBを得ようとしています

略語の説明

DFS:Depth First Search
BFS:Breadth First Search
LB: Lower Bound

2020年6月3日水曜日

Visual C++2019での挙動差異

Algorithm4のインスタンスの一部が動きませんでした。
調べたところ、
次の箇所、std::mapの挙動が違いました。
mapがemptyであるとき、0が入ると思っていたのですが、デバッグモードでは、0、最適化(Release Mode)を行うと、1が入ってしまいました。

  map[xx]=map.size();
  
言語仕様上、正しい動作は分からないのですが、とりあえず、明示的に次のような対処を行いました。

 int size=map.size();
  map[xx]=size;
  
Runtime自体は、変わっていないようですので、恐らくコンパイラの差異なんでしょう。いつものことですが、コンパイラを変えると何かしら、こうした問題が発生します。

2020年6月2日火曜日

各国祝日の調査

AWS対応は、ほぼCodingが終わりました。
後は、Docker SDK提供とIAM Role/Principal等のインタフェース定義対応が残るのみです。

SC3のデスクトップブリッジ対応は、海外から始めようと思います。

そうなると問題は、

1)英語マニュアル
2)ソフトの各国対応

になります。中でも祝日定義をどこから拾ってくるか問題です。

WindowsでAPIであればよいのですが、UWPでもなく、方法としては、次の二つになるかと思います。

1)https://qiita.com/richmikan@github/items/9090407e3ab9cd3e80b2
2)https://hnw.hatenablog.com/entry/2019/04/13/212054

1)は、オンライン 2)は、オフラインになります。要検討です。デスクトップブリッジは、ネットアクセスを許容すると思うので、1)が第一候補です。

とりあえず、SC3日本語マニュアルを作ります。

2020年6月1日月曜日

SC3のプロモーション動画を作りました

3交代正循環を題材にして動画をSC3で作ってみました。

https://youtu.be/0vuGpXbO2Wc

AVIをそのままアップロードしましたが、文句を言われませんでした。