2019スケジュールナースのブログ2019年12月26日木曜日よくある誤解https://www.qmedia.jp/misunderstanding-of-qc/とてもよくまとまっていると思います。https://www.aist.go.jp/aist_j/press_release/pr2018/pr20181009/pr20181009.htmlこちらについても懐疑的な印象を持っています。二つの課題があると思います。1)厳密解を出せないSimulatedAnnealingに限らず、MetaHeuristicsによる解法は、基本的に厳密解を出せません。私の知る限り、厳密解を出せるのは、数理的解法か、SAT解法かのどちらかしかありません。(その両方を備えたソルバがSC3です。)2)解がない原因を示せない制約モデルの設計・開発において、ハード制約とソフト制約を分けて設計を行うのは、人間が行う上では、必須であると思います。ハード制約の解がない原因を知る機構が設計においては、必須になります。(これも私の知る限りSC2/SC3のみの機能です。)そもそも、通常のNSP問題においても、組み合わせ解空間が宇宙の素粒子数を超えることはよくあります。それでいて厳密解が求まる場合もあります。巡回セールスマンの世界記録も数理解法によるものです。同じベンチマークで論じるべきだと思います。今年は、曲がりなりにもSC3をリリース出来、長年実現できなかったNSP記述の一般化形態をリリース出来ました。お正月にかけて、良い結果を出せるようにソルバAl4の改善を行います。本年度は、業務終了です。ありがとうございました。実装の参考こちらが、参考になります。https://jetbead.hatenablog.com/entry/20180120/1516387673いずれにしても問題は、NSPで近傍をどのように表現するかではないかと思います。こちらも先人の知恵を拝借したいと思います。共有2019年12月24日火曜日D-wavesを用いたNSP論文が公開されています。残念ながら、既存の方法との比較がないので、評価のしようがありません。https://www.itmedia.co.jp/news/articles/1911/20/news149.htmlhttps://www.itmedia.co.jp/news/articles/1906/03/news033_3.htmlのように否定的な記事もあります。共有2019年12月23日月曜日SchedulingBenchmarkサイトが更新NurseSchduling問題の他に、Retail問題が追加されています。これが線形モデリングならば、フェーズタスク形式に記述することでSC3でも解けるはずです。残念ながら、線形モデリングではなく、2次形式によるモデリングなので、線形ソルバでは解けません。NurseScehduling問題の方は、Instance20の最新BestValueが記録更新されています。私のソルバは、LB・UB既に確定させており、真の厳密解は、4769です。報告すれば、認められるでしょうが、英語サイトが準備出来ていないこともあり未だ報告していません。SC3による日本独自のベンチマークと、SchedulingBenchmarks及びNRC2のベンチマーク結果が整備出来た後の公開になります。その意味で、Algorithm4の改善は必須です。共有2019年12月22日日曜日シミュレーテッドアニーリングサーヴェイとりあえず勉強中です。http://www.orsj.or.jp/~archive/pdf/bul/Vol.40_05_268.pdfメタヒューリスティクスは、これまでのところ実装する必要を感じていません。CPLEX/Gurobiには適わないという結果になっています。問題によっては、より良い結果を生む可能性を否定はできません。これを実装するとSAT系、数理系にプラスすることになり、ソルバーとしての価値が高まるかもしれません。研究してみようと思います。共有2019年12月21日土曜日ランダム予定生成機能127Aから上記をサポートしています。正循環と深準型で、どちらがロバストな制約パターンは、予定負荷をかけて評価するしかありません。実休みパターンを数種類用意すれば、良いのですが、それも面倒なときは、ランダムに予定生成する機能を使います。ランダムとは言っても、休みとそうでないときでは、一定の法則性があると思うので、それぞれの曜日用に生成するようにしています。(単純なランダムでは、全く意味がありません。5日希望まで許されている職場で、平日休日問わずにランダム生成すると、意外なほど耐性がなく実態と乖離が生じます。5日希望がまがりなりにも成立しているのは、休日には、休み希望が集中するという法則性に助けられている側面があります。)曜日集合の上から4番目までを選択することができます。制度設計用にご活用ください。共有2019年12月20日金曜日127Aをリリースしましたマニュアルも更新しました。Python制約プログラミングマニュアルも更新しました。127Aでは、GUIの制約コントロールをPythonで行っています。今後は、Algorithm4の改善を行う予定です。来年は、AWS検討を行う予定です。任意の制約をEnable/Disable出来るようになったので、デモを作りました。添付したので、Win32のAPIを呼ぶことができます。ctypesについては、こちらが詳しいかと。デモは、正循環パターンと深準夜パターンをGUIで切り替えます。sc3.print('行制約正循環をEnableしました。sc3.print('深準パターンをEnableしました。')制約自体は、正循環も深準パターンもGUIで記述しています。制約グループの切り替えをPythonで行っている、ということです。下がログになりますが、Propertyファイルの生成した後、Python記述が評価されGUIダイアログが表示されます。ユーザがボタンを押すとYES/NOが戻ってきて、後は、制約のオンオフを行っているだけす。コンパイルの準備中ソルバを呼び出し中です。propertyファイル生成を開始します。propertyファイル生成が終了しました。行制約正循環をEnableしました。充足解を書き込みました。used.解探索が終了しました。(秒)解が得られました。ところで、重み10000は、オプションハード列基数制約のソフト化で生成した自動重みです。これをDisableすることにしましょう。オプションを設定するには、SetSolveingParameterを使います。problem.jsonを参照すれば、オプションコマンド名が分かります。sc3.print('行制約正循環をEnableしました。sc3.print('深準パターンをEnableしました。sc3.print('ハード列基数制約のソフト化を実行します。sc3.print('ハード列基数制約のソフト化を実行しません。')ところで、個別の制約をオンオフするには、次のようにドットで記述します。上位のグループ制約をオフにすると効きませんのでご注意ください。深準型で、公休深夜パターンを一回以上という方式検討依頼がありました。8回もはや、かなり少なくなったクラッシックパターンですが、公休深夜パターンを1回以上、というのが入るものかどうか、という依頼です。コーディングすること30分、求解40秒で次の解となりました。(他に、前後に夜勤のない土日休みが1回以上という看護協会仕様の制約も入れています。26人というのが、夜勤回数全員を8回以下を満たすための味噌なんですね。Pureな深夜準夜パターンしかない解を初めてみました。面白いので、次のリリース時にプロジェクトサンプルに入れておきます。共有2019年12月17日火曜日pythonの変数を日本語化の実装その2実装後による結果です。126Aでは、次のようにプロパティファイルを生成しました。group#column_constraintsA=(True,('今月',今月),'',PH0,('A',A),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)B=(True,('今月',今月),'',PH0,('B',B),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)C=(True,('今月',今月),'',PH0,('C',C),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)D=(True,('今月',今月),'',PH0,('D',D),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)E=(True,('今月',今月),'',PH0,('E',E),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)F=(True,('今月',今月),'',PH0,('F',F),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)G=(True,('今月',今月),'',PH0,('G',G),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)H=(True,('今月',今月),'',PH0,('H',H),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)a=(True,('今月',今月),'',PH0,('a',a),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)b=(True,('今月',今月),'',PH0,('b',b),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)c=(True,('今月',今月),'',PH0,('c',c),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)d=(True,('今月',今月),'',PH0,('d',d),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)e=(True,('今月',今月),'',PH0,('e',e),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)f=(True,('今月',今月),'',PH0,('f',f),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)NBデスク1人=(True,('NBD1人',NBD1人),'',PH0,('X',X),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)NBデスク0人=(True,('NBD0人',NBD0人),'',PH0,('X',X),全スタッフ,1,0,-1,-1.000000,-1.000000,0,100)NB夜0人=(True,('NB夜0人',NB夜0人),'',PH0,('Y',Y),全スタッフ,1,0,-1,-1.000000,-1.000000,0,100)NB夜1人=(True,('NB夜1人',NB夜1人),'',PH0,('Y',Y),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)NB夜2人=(True,('NB夜2人',NB夜2人),'',PH0,('Y',Y),全スタッフ,1,2,2,-1.000000,-1.000000,0,100)NB夜3人=(True,('NB夜3人',NB夜3人),'',PH0,('Y',Y),全スタッフ,1,3,3,-1.000000,-1.000000,0,100)MS+あり=(True,('MS+の日',MS+の日),'',PH0,('g',g),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)MS+なし=(True,('MS+がない日',MS+がない日),'',PH0,('g',g),全スタッフ,1,0,-1,-1.000000,-1.000000,0,100)DS+あり=(True,('DS+の日',DS+の日),'',PH0,('h',h),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)DS+なし=(True,('DS+がない日',DS+がない日),'',PH0,('h',h),全スタッフ,1,0,-1,-1.000000,-1.000000,0,100)NS+あり=(True,('NS+の日',NS+の日),'',PH0,('i',i),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)NS+なし=(True,('NS+がない日',NS+がない日),'',PH0,('i',i),全スタッフ,1,0,-1,-1.000000,-1.000000,0,100)NB朝0人=(True,('NB朝0人',NB朝0人),'',PH0,('W',W),全スタッフ,1,0,-1,-1.000000,-1.000000,0,100)NB朝1人=(True,('NB朝1人',NB朝1人),'',PH0,('W',W),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)NB朝2人=(True,('NB朝2人',NB朝2人),'',PH0,('W',W),全スタッフ,1,2,2,-1.000000,-1.000000,0,100)NB朝3人=(True,('NB朝3人',NB朝3人),'',PH0,('W',W),全スタッフ,1,3,3,-1.000000,-1.000000,0,100)NB遅0人=(True,('NB遅0人',NB遅0人),'',PH0,('V',V),全スタッフ,1,0,-1,-1.000000,-1.000000,0,100)NB遅1人=(True,('NB遅1人',NB遅1人),'',PH0,('V',V),全スタッフ,1,1,1,-1.000000,-1.000000,0,100)NB遅2人=(True,('NB遅2人',NB遅2人),'',PH0,('V',V),全スタッフ,1,2,2,-1.000000,-1.000000,0,100)NB遅3人=(True,('NB遅3人',NB遅3人),'',PH0,('V',V),全スタッフ,1,3,3,-1.000000,-1.000000,0,100)列制約グループ1フェーズ={'A':A,'B':B,'C':C,'D':D,'E':E,'F':F,'G':G,'H':H,'a':a,'b':b,'c':c,'d':d,'e':e,'f':f,'NBデスク1人':NBデスク1人,'NBデスク0人':NBデスク0人,'NB夜0人':NB夜0人,'NB夜1人':NB夜1人,'NB夜2人':NB夜2人,'NB夜3人':NB夜3人,'MS+あり':MS+あり,'MS+なし':MS+なし,'DS+あり':DS+あり,'DS+なし':DS+なし,'NS+あり':NS+あり,'NS+なし':NS+なし,'NB朝0人':NB朝0人,'NB朝1人':NB朝1人,'NB朝2人':NB朝2人,'NB朝3人':NB朝3人,'NB遅0人':NB遅0人,'NB遅1人':NB遅1人,'NB遅2人':NB遅2人,'NB遅3人':NB遅3人}column_constraints={'列制約グループ1フェーズ':列制約グループ1フェーズ}当然ながら、この状態では、次のようにコンパイルエラーとなります。コンパイルの準備中ソルバを呼び出し中です。propertyファイル生成を開始します。propertyファイル生成が終了しました。'「@地上波+BS」=[0,4,10,11]\n'))解探索が終了しました。(秒)解を見つけることが出来ませんでした。同じプロジェクトですが、127Aでは、次のように生成されエラーとはなりません。CHARは、スキップされます。すると同じ名前になってしまうものが出てしまうので、Prefixで別な名前にしています。(元の名前が同じならば、同じ名前に変更されます。Day集合名とColumn制約名が同じ場合があったので、制約名のマッピングは別にして実装しています。これは、よくありそうな事象であり、自然にその対策になります。いずれにしても、元の制約名は推定可能なので、このファイルを参照すれば、Pythonで書くのに障害にはなりません。チェックを外してもプロパティファイルを生成しています。(これは、少し複雑な事情によります。)GUIでは、チェックを外せば、Day集合記述がなくてもエラーとはなりませんが、Propertyファイル生成時は、エラーとなります。グループの適用を外せば、Propertyファイルは生成されません。この一点だけ、ご注意頂ければ、任意のGUIをPythonプロパティ形式に落とせます。これによりすぐにPython記述を始めることができます。み変更、許容エラー数変更が可能になります。Pythonで制約を記述しなくてもプロジェクト全体に渡る制約のコントロールが可能となります。以上は、全ロケールで適用となるので、プロパティファイルについては、(日本語のみならず)国際化完了です。次回は、pythonでの制約コントロール方法について述べます。共有2019年12月16日月曜日pythonの変数を日本語化の実装その1前回構想に基づいた実装です。変数の頭とそれ以外で、禁止文字が異なるので、tableは、2回に分けて生成します。連続charは、一まとめにしたC++2次元テーブルを生成するようにしています。以下は、頭文字禁止の場合のtable生成コードです。生成されるtableをそのままC++ソースに貼り付けています。頭禁止で、6万、頭以外で10万程度しかありませんでした。ユニコード全体でも高々100万程度のオーダです。意外に多くはないです。charだったらスキップして、prefixを付加します。共有2019年12月15日日曜日ユーザエクスペリアンス最も難しいのは、ソルバーではなく、制約プログラムですらありません。ユーザの意図を組んで制約を作り上げていく作業です。タイプは、二つに別れ全くサポートの不要な方とそうでない方の2極ですが、後者の場合、3ヶ月以下で物になった経験はありません。中には、半年経っても未だ、稼動すら行き着いていないプロジェクトもあります。作業時間だけで取ったとしても、10万円では殆どの場合ペイしません。(反対に、前者の方には申し訳ないので、サポートとソフト費用は別にするストアアプリ化を行います。後者の方にどのようなアプローチがよいのか、悩みつつも、良い開発スタイルであると感じるのは1)ユーザ実勤務表ベースでのExcelによるやり取り(ユーザ実勤務表に制約達成度を数値化・可視化していく)2)ユーザ希望仕様をそのまま実装しない。(ユーザ経験に基づかない新規制約は、結局実現不能が多い。思いつき制約には付き合わない。背後にある本当の制約を推し量ることが重要)3)実勤務表作成者に評価・FBをして頂く。(上位管理者ではNG)です。思いつき制約には要注意です。「どうせ機械に生成させるのだから」という発想で、やられると、苦労して実装したとしても、他の制約とのPriorityとの兼ね合いで殆ど有効に動作しない場合が多く、ない袖は振れない・絵に描いた餅を体感するばかりか、何ヶ月経っても収束しない事態になってしまいます。実績のあるルールにこだわる理由がここにあります。いずれにしても、ダイバーシティとか人口知能とか、そういった技術範疇ではなくて、実勤務表作成者の頭の中をいかにして整理してあげるか、これに尽きます。実担当者は、本当に色々な事に思いをはせ、気を配りながら作っています。それが系統的に整理されてはいません。その時々の思いつきに左右されることも多いです。それを一つ一つ拾い上げ、本当に必要なルールだけを制約化していくことは、骨が折れる作業です。業界の常套宣伝文句の「簡単」は、簡単ではないのです。共有2019年12月14日土曜日Pythonの日本語で使えない変数を使えるようにする構想こちらで、確認プログラムを見ることが出来ます。これを実行し、結果を予め辞書として持っていて、除去、適当な接頭辞をつければ、とりあえず雰囲気をある程度残したまま使えるようになると思います。(こちらは、後で述べるロケール問題(国際化)でも必要となる作業です。)どの道、変数名は、生成されるプロパティファイルを参照することになるので、多分これでも支障がないでしょう。年末年始など、特定のグループ・制約をPythonで、Enable・Disable出来ると、全てをPythonで書き換えなくても既存のGUI記述を生かすことが出来ます。すると、GUI上では、オフだったものが実際にはオンになるものが出てきてしまうものが出てきます。この辺を対処するために、各行列制約のレベルや重みも変える機能も必要になります。最終的にはPythonで設定した状態でSolveされます。以上の対応が出来ると、「月」から自立的にPythonで必要な制約のEnable・Disableが出来、究極のメンテナンスフリーが実現できることになります。長年願って実現出来なかった機能ですが、やっと形が見えてきました。狙っている形態は、SEがメンテナンスフリーの制約プログラムを書きます。ユーザは、Excel毎月、で必要な稼働日集合、タスク集合、スタッフプロパティ、予定シフト集合を設定してインポートするだけで、特にオンオフ・調整箇所はないことです。これは、設計と使い方の分離を意味し分業体制が可能となる素地となります。近いうちに実現したいと思います。日曜出勤したらその週のどこかで休むというご要求を受けたのですが、以下のように実装し、ご要求そのままではありません。理由は、1)端数処理が面倒2)解空間をなるべく狭めたくはないその週となると、月はじめ、月終わりの処理が面倒になります。休みというシフトも区別する必要があり、例えば代休というシフトを新設する必要があります。今月以外に見る必要も生じます。第2の問題は、その週に限定すると、解空間が狭まります。そこで、要求のエッセンスを満たす方式として、日曜出勤した数だけ代休するという処理にしました。すれば、その週に限定することはないので、解空間の減少を極小にすることができ、なおかつ月内処理に限定できるので、実装が簡単になります。sc3.AddHard(sc3.SeqComp(日_list,代_list),s)日曜出勤した分だけ、代休(青色)しているのが分かると思います。ところで、SeqCompが意外に使い勝手があり、あることを思いつきました。次回これを拡張することを考えます。1人休みのときに病棟看護師は外来勤務という制約の実装をどうするかという問題です。外来看護師というグループがあり外来専門で外来を行っています。このグループ内で休みがあったとき、休み人数分病棟看護師から持ってくるという制約になります。Σ日勤@外来看護師+Σ外来@外来担当の病棟看護師==Nという制約にすればよいことになります。外来看護師数が増減してもPythonで書けば、動的に処理が可能でメンテナンスフリーに出来ます。共有2019年12月10日火曜日エラー解析は難しい125Aで明らかなハードエラーの解析ルーチンを改善したつもりだったのですが、時間がかかり過ぎるインスタンスがあることが判明しました。こちらは、鋭意改善の思索中です。ソルバーという意味では、MIPソルバーも含めて、数々のソルバーがこの世には存在します。原因解析と真面目に向き合っているのは、SC2/SC3のみだと思います。専門家が使うのではない、NSPのモデルを開発・改変・変更する過程において、必須となる機能です。宣伝しますが、こちらの基本特許は、未だ使用権は私のみです。基本的な考えは、この特許に基づいていますが、いまだに実装上では、色々な事を考えないといけなくて経験とノウハウを要する奥の深いところです。共有2019年12月9日月曜日126Aをリリースしましたマニュアルも更新しました。Python制約プログラミングマニュアルも更新しました。Importの速度改善を行いました。■フェーズタスクモードのサンプルを追加しました。共有2019年12月7日土曜日125Aをリリースしました125Aよりフェーズ・タスクをサポートします。私の造語?)を記述できるようになります。Tutorialの整備はこれからです。sc3.print('\n')解説です。SC3のPythonインタープリタは、InstallFolderのsim_engineにあります。ExcelのCSV出力(utf8を指定)のフォルダに移動します。このとき、逆スラッシュがExcapeされないようにr’をつけておきます。encodeの指定をutf-8にして読むことが出来ました。これで、Excel直ではないにしろExcelで管理するデータを読むことができるので、よりPythonでダイナミックに制約し易くなるのではないかと思います。現在、チュートリアルで、タスク記述例の作成を行っています。この数独では、シフトは1個、日勤のみ使っています。Dayを3つのフェーズに分けて、各フェーズには、9個のタスクのうち1個だけが入ります。Pythonでの記述は以下の通りです。Algorithm1で実装しました。この機能は、スタッフの予定を変更せざるを得ないときの別解を提供します。しても看護品質・ケア品質は落としたくない場合は、スタッフの予定を変更せざるえません。そんなとき、スタッフと調整するときに別解を持っておけば、調整し易いでしょう。上の例では、4つの予定変更が余儀なくされていて、その別解を求めています。赤枠部がそれになりますが、微妙に、しかし確実に異なる組み合わせの解のみが掲示されています。この例では、設定を100解にして、62解まで求まりました。実用的には、GUIの速度的に、数解まででしょう。同じ、パラメータで、Algorithm0(旧SC2)でやってみると、21個のエラーに対する別解を求めていました。同じ、パラメータで、Algorihtm4でやってみると、厳密解が3であることが5秒足らずで判明しました。コンパイルの準備中ソルバを呼び出し中です。充足解を書き込みました。充足解を書き込みました。充足解を書き込みました。充足解を書き込みました。充足解を書き込みました。充足解を書き込みました。5.730(CPU秒)解探索が終了しました。(秒)解が得られました。(これより、Algorithm1の解は近似解であることが分かります。)歩みは遅いですが、確実に性能向上しています。s=staffdef[person]+'を今月支援禁止にします。スタッフに0がついています。Pythonで、それを拾ってきて、今月がその月になったときだけ制約するという究極のメンテナンスフリーを狙った記述になります。(この部分だけ取れば、Excelを毎月読ませることも不要)Python記述で必要になるのは、今月が該当月に相当するかどうかの判定部です。それには、今月のmonthを取得するルーチンが必要となり、それが、get処理月()になります。正規表現ライブラリをimportして、正規表現で処理しています。正規表現で、シンプルに文字列処理できます。pythonインタプリタがどこまで頑張れるかよく分かっていないのですが、標準ライブラリについては大丈夫なようです。巨大なライブラリ、例えばopenpyxlは、動きませんでした。(Atom)自己紹介スケジュールナーススケジュールナースという勤務表自動作成ソフトを開発・販売しています。本ブログでは、ユーザ様の要求仕様を「制約プログラム」に変換していく様子をメインに紹介していきたいと思います。