2019年5月24日金曜日

LinkEdin登録

GUIのGridControlは、Syncfusion社のCommunityLicenseで使っています。見栄えはよいしMicrosoftlよりはずっと使いやすいのですが、Taskが数十あって、Staffが100人を超えるプロジェクトではとても遅いです。なので、現在、顧客との開発では、GUIは使わず、Excelベースで入出力しています。で、改善版はないかなと思って、Syncfusionを覗いたのですが、現在は、LinkEdinかXingの登録をしていないと入れません。そういった事情で、LinkEdin登録をしました。写真とかあまり気が進まないのですが、しょうがないですね。ともあれ、これで使えるようにはなりました。未だ最新版を使ってはいないのですが、そのうちトライしてみようと思います。

2019年5月20日月曜日

Excelで勤務表の制約を入力、解出力もExcelで!

SC3では、Excelを直接に読み書きできます。フォーマットは供給するテンプレートFILEに基づいて記述する必要があります。直接にSC3をいじるのは、次の3ステップのみです。SC3をいじるところは、殆どありません。

企業向けの機能ですが、例えば、3ヶ月単位で残業数をスタッフ単位ごとに個別調整したいときに、
年内での残業数をExcelで管理しておいて、月単位の目標値をExcelからの計算結果から自動設定する、といった用途にも使用可能と思います。

ITベンダにスケジューリングシステムを依頼する最低でも導入費用として数百万円かかってしまいあまり現実的ではありません。かと言って、Excelのソルバでは解けない問題も多いし、ソルバを開発する技術も暇もない、そういった方にお勧めです。GUIでかなりの部分が記述できますし、
GUIで記述しきれないところは、Pythonで記述できます。これで、記述できない規則は多分ないでしょう。世界最高峰のソルバがあっても、簡易に制約を記述できなければ宝の持ち腐れです。スケジュールナースⅢ python プログラミングマニュアルを準備中です。本ブログでもこれから沢山紹介していきます。

スケジューリングシステム構築のコンサルティングを承っております。サポートまでご用命ください。

2019年5月10日金曜日

Pythonによるタスクの列挙

SC3では、次のようにして、タスクとシフトの列挙をすることが出来ます。
import sc3
#定義されているすべてのタスクの出力
for タスク名 in taskdef.keys():#taskdefは、辞書
 sc3.print(タスク名+'の工程担当能力のあるのは次の人達です。\n')
 for 人 in taskdef[タスク名]:
  sc3.print(staffdef[人]+'\n')
  
#定義されているすべてのシフトの出力
for シフト名 in shiftdef.keys():#shiftdefは、辞書
 sc3.print(シフト名+'がありえるのは次の人達です。\n')
 for 人 in shiftdef[シフト名]:
  sc3.print(staffdef[人]+'\n')
これは、次のpythonファイル  プロジェクト名_property.py(プロジェクトファイルフォルダーに出力) で定義されている為です。暗黙のうちにインポートされています。ファイルは、reference only用です。
公休=[0,1,2,3,4]
勤務1=[0,1,2,3,4]
勤務2=[0,1,2,3,4]
勤務3=[0,1,2,3,4]
勤務4=[0,1,2,3,4]
年休=[0,1,2,3,4]
統括作業長=[4]
作業長=[0,1,2,3,4]
...
#shiftdef
shiftdef={'公休':公休,'勤務1':勤務1,'勤務2':勤務2,'勤務3':勤務3,'勤務4':勤務4,'年休':年休}
#taskdef
taskdef={'統括作業長':統括作業長,'作業長':作業長,...

解のログです。

コンパイルの準備中ソルバを呼び出し中です。
統括作業長の工程担当能力のある人は次の人達です。
スタッフ5
作業長の工程担当能力のある人は次の人達です。
スタッフ1
スタッフ2
スタッフ3
スタッフ4
スタッフ5
...
NoTaskVarの工程担当能力のあるのは次の人達です。
スタッフ1
スタッフ2
スタッフ3
スタッフ4
スタッフ5
公休がありえるのは次の人達です。
スタッフ1
スタッフ2
スタッフ3
スタッフ4
スタッフ5
勤務1がありえるのは次の人達です。
スタッフ1
スタッフ2
スタッフ3
スタッフ4
スタッフ5

これで、任意のタスク変数、シフト変数をPython上で呼ぶことが出来るようになりました。 シフト変数は、リニアにアサインされているので、リニアに数字インデックスでアクセスできます。 それに対して、タスク変数は、Phaseに対して、Person毎に異なるインデックスになります。それ故、 タスク変数は、リニアにアクセスすることは出来ず、定義されたタスクだけにアクセスすることができます。 (Person毎に担当できる工程は変わり、しかもその工程は何十もあるので、リニアアクセスにするのは、メモリ的に 非効率です。) そのために、Dictionaryを用いてstringで、上のようにタスクを指定する必要があります。

2019年5月7日火曜日

人工知能学会会員申し込み

WEBで申し込みをしました。
一応、SAT関係は、こちらになります。新しい論文も読めるので楽しみです。


2019年4月27日土曜日

PHS所持待機問題の構想

訪問看護の現場で、PHS所持待機の日数を等分に割り振りたいという問題があります。
(こちらは、通常のシフトスケジューリング問題とは異なる対応が必要になりますが、そのことについては、別の機会に。)

その等分というのは、長期休暇が入る3ヶ月、もしくは、1年で見て、各スタッフ等分に、という意味です。そうなると、月々の設定だけではなくて、長期休暇予定を見据えて月々に目標値を配分するという管理が必要になってきます。まず管理シートが必要となります。これを自前で持つのも一案ですが、Excelを直に読み書きした方がベターと判断しました。GUIは、C#なので、Excelを操作できるC#ライブラリをSurveyしてみました。ClosedXMLがよさそうですので試してみることにしました。

これとは別に、スタッフ数100人を超える職場で、各スタッフのシフト、タスク、年間予定シフトを手入力するのも大変です。なので、Excelシートを指定してインポートできるようにします。

2019年4月26日金曜日

数独をPython制約で解く

import sc3
for day in 全日: #列制約
 for シフト in 全シフト:
  V=[]
  for 人 in 全スタッフ:
   V.append(sc3.GetShiftVar(人,day,シフト))
  sc3.AddHard(sc3.SeqLE(1,1,V),'')
for 人 in 全スタッフ: #行制約
 for  シフト in 全シフト:
  V=[]
  for  day in 全日:
   V.append(sc3.GetShiftVar(人,day,シフト))
  sc3.AddHard(sc3.SeqLE(1,1,V),'')
for 人 in スタッフブロックトップ: #ブロック制約
 for シフト in 全シフト:
  for  day in ブロックトップ日集合:
   V=[]
   for  i  in range(3):
    for  j in range(3):
     V.append(sc3.GetShiftVar(人+i,day+j,シフト))
   sc3.AddHard(sc3.SeqLE(1,1,V),'')
  
僅か21行で数独が記述出来ています。神戸大学のCoprisには 及びませんがそれなりに簡潔に記述出来ているのではないと思います。SeqLEの書式は、(min,max,List)で、ハード制約専用です。
min==max==1なので、LeastOne&&MostOneつまり、OnlyOneということです。

Pythonの制約ステートメント

import sc3

v1=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-27  ', '  オンコール ')
sc3.AddHard(v1,' 川畑拓也2017-05-27    オンコール')


制約するには、シフト変数v1を取り出します。シフト変数は、3次元で、Person、Day,Shiftの順にGetShiftVar関数で取り出します。 引き数は、GUIで定義した文字列、もしくは、配列Indexです。または、任意の組合せ(文字列/配列Index)になります。どちらもPropertyファイルを参照して、Validな名前、範囲を確認することが出来ます。文字列の場合、空白はあっても構いません。(Trimされます。) で、取り出した変数をアサートするには、AdddHard関数を呼び出します。AddHard関数は、1番目に変数、2番目にコメントになっています。上記は、川畑拓也の2017-5-27の勤務はオンコールであること、というハード制約になります。 下が実行結果です。確かに、オンコールのラベルであるになっています。

同様にして、日付を追加したソースが次です。
import sc3

v1=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-27  ', '  オンコール ')
sc3.AddHard(v1,' 川畑拓也2017-05-27    オンコール')

v2=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-28  ', '  オンコール ')
sc3.AddHard(v2,' 川畑拓也2017-05-28    オンコール')

v3=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-29  ', '  オンコール ')
sc3.AddHard(v3,' 川畑拓也2017-05-29    オンコール')

v4=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-30  ', '  訪問看護 ')
sc3.AddHard(v4,' 川畑拓也2017-05-30    オンコール')
実行結果は次です。
 

次は、Andに関するどれもValidな構文です。And()関数の引き数は、4個までサポートしています。2項演算子&を使えばその制限はありませんが、3個以上の演算については、リストを使うことを推奨します。
import sc3

v1=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-27  ', '  オンコール ')
v2=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-28  ', '  オンコール ')
v3=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-29  ', '  オンコール ')
v4=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-30  ', '  訪問看護 ')
sc3.AddHard(v1&v2&v3&v4,' 川畑拓也2017-05-30パターン    オンコール')

vlist=[v1,v2,v3,v4]#3個以上はリストを推奨
sc3.AddHard(sc3.And(vlist),' 川畑拓也2017-05-30Vlistパターン    オンコール')

v5=sc3.And(v1,v2,v3,v4)#引き数は4個まで
sc3.AddHard(v5,' 川畑拓也2017-05-30Andパターン    オンコール')
ハード制約にはコメントをつける
コメントは、空白でSPLITされる任意の文字列とします。 上の記述は、3つのANDの制約は、お互いが等価な制約です。感心しない記述ですが、お互い矛盾しないのでエラーは出ません。 ここで意図的にエラーを作ってどのような 事象になるかを見てみます。具体的には、v4をInvert(Not)してみます。制約BC間は、矛盾しませんが、制約AB,AC間では矛盾が起こります。
import sc3

v1=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-27  ', '  オンコール ')
v2=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-28  ', '  オンコール ')
v3=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-29  ', '  オンコール ')
v4=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-30  ', '  訪問看護 ')
sc3.AddHard(v1&v2&v3&~v4,' 川畑拓也2017-05-30パターン    オンコール')#制約A)故意にエラーを生成

vlist=[v1,v2,v3,v4]
sc3.AddHard(sc3.And(vlist),' 川畑拓也2017-05-30Vlistパターン    オンコール')#制約B

v5=sc3.And(v1,v2,v3,v4)
sc3.AddHard(v5,' 川畑拓也2017-05-30Andパターン    オンコール')#制約C
コンパイルの準備中ソルバを呼び出し中です。
 python propertyファイル生成を開始します。
 python propertyファイル生成が終了しました。
 満たしていないハード制約の集約化を行います。
 o=11 Time=3.153(CPU秒)
 o=10 Time=3.183(CPU秒)
 o=9 Time=3.245(CPU秒)
 o=8 Time=3.362(CPU秒)
 o=7 Time=3.425(CPU秒)
 o=6 Time=3.516(CPU秒)
 o=5 Time=3.593(CPU秒)
 o=4 Time=3.666(CPU秒)
 o=3 Time=3.773(CPU秒)
 o=2 Time=3.805(CPU秒)
 o=1 Time=3.907(CPU秒)
 f=0 Time=3.969(CPU秒)
 Optimum ConUB found
 b=1 Time=4.039(CPU秒)
 以下に矛盾または満たしていない可能性がある制約を示します。
  ● 川畑拓也2017-05-30パターン
解探索が終了しました。 4 (秒)
解を見つけることが出来ませんでした。
上記は、ソルバーが充足解を見つけられないと判断し、充足しない原因を探すために、どの制約を外せば、充足するようになるかという エラーの最小化アルゴリズムが実施された結果です。結果、一つの制約を外せば、全て満たされることが示されています。 制約の開発・設計過程においては、上記のようなことが割りに頻繁に起きます。そのとき、コメントがブランクだったらどうなるでしょう? 矛盾している制約の数が1個であることは分かりますが、コメントがブランクだと、それがどこなのかは、知ることができません。ナーススケジューリングの場合、制約の数は、数万を越えることは普通にあります。面倒でも、ハード制約には他の制約と区別するコメントをつけておくことを強くお勧めしたい理由がここにあります。SC3は、数理最適化系のソルバーも搭載しますが、こちらの場合は、運がよければ目的関数値が妙にでかくなる、大抵の場合は、線形制約ソルバーが充足しないので手も足も出ません。結果、どこが問題かは、見当をつけることさえ困難です。厳密解を目指す場合、数理最適化系が有利になる場合がありそちらのソルバーを最終的に使う場合でも、制約記述の開発・設計には、こちらのモードで行うのがよいでしょう。
import sc3

v1=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-27  ', '  オンコール ')
v2=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-28  ', '  オンコール ')
v3=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-29  ', '  オンコール ')
v4=sc3.GetShiftVar(' 川畑拓也 ',  ' 2017-05-30  ', '  訪問看護 ')
sc3.AddSoft(v1&v2&v3&~v4,' 川畑拓也2017-05-30パターン    オンコール',  1  )#制約A)故意にエラーを生成レベル1でソフト制約

vlist=[v1,v2,v3,v4]
sc3.AddHard(sc3.And(vlist),' 川畑拓也2017-05-30Vlistパターン    オンコール')#制約B

v5=sc3.And(v1,v2,v3,v4)
sc3.AddHard(v5,' 川畑拓也2017-05-30Andパターン    オンコール')#制約C

制約Aをソフト制約化するには、AddHardをAddSoftに置き換え、最後にレベルを加えます。上では、レベル1に設定しました。 SC2では、エラーをカウントするという意味で、INV化(Not化)する必要がありましたが、SC3では、必要なく、上記変更だけで事足ります。 ソフト制約化することにより下のように充足することになります。

python propertyファイル生成を開始します。
 python propertyファイル生成が終了しました。
 解探索を開始します。2.370(CPU秒)

 User Soft Constraint Level 1
 o=1 Time=2.454(CPU秒)
 f=0 Time=2.484(CPU秒)
 Optimum ConUB found
 o=1 Time=2.563(CPU秒)
 o=1 Time=2.587(CPU秒)
 b=1 Time=2.603(CPU秒)
 充足解を書き込み中です。


SC3、Pythonによる言語制約について概観しました。Python言語制約の公式Tutorialは、6月にリリース予定です。SC3で追加した、Phase/Task機能、さらにPythonによる言語制約により、 およそ勤務表と名のつくスケジューリング問題で出来ない勤務表はないと思います。SC2は、元々他の勤務表ソフトでは記述不能な、複雑で込み入った仕様の勤務表について、トップを独走中でした。今回の機能強化により、工場の工程管理、コールセンター等、大規模な勤務表分野にも参入します。さらに、SC2では、基本1ヶ月単位でしたが、3ヶ月・1年といった単位での管理のご要望がありまして、そちらについても対応予定です。