2025年4月30日水曜日

DPSのincremental化 NSP未解決問題の解決に向けて

https://www.nurse-scheduling-software.com/japanese/publications/lecture_notes_for_basic_modeling.pdf#page=2

で一応アナウンスはしました。が、問題は全然解決していません。



kissatがincrementalになるのを待っているのですが、中々incrementalになってくれません。

これによれば、今年も望み薄なので、実装することにしました。

More incrementality features? · Issue #3 · arminbiere/kissat

その後に、DPSに組み込みを行います。これが、Alogrithm1の後継Algorithm5になります。

その他に、LPソルバの改善、メモリ消費改善、数理ソルバとの融合を経て、ようやく収束速度改善に至ります。



今年末までにNSP未解決問題を解決する工程を策定しました。今年末には、さらにパワーアップしたスケジュールナースをリリース出来ると思います。また長年温めていた数理ソルバが実用域に入ってくると思います。これにより厳密解を提示できるチャンスが格段に増えると期待されます。
2017年に発した一つの疑問に対する答えの提示になっているとよいのですが。

来るべきイノベーションに向けてキャッチを考えました。

「(あなたのソルバは)物理限界まで最適化できますか?

2025年4月29日火曜日

年間累計処理

 コア数チェックプロジェクトで、記述を追加とExcelシート更新をしました。



今月解シートに解を出力しますが、その際オプションで、出力する項目行を指定しています。

コア数チェックプロジェクトでは、それが”行事予定”にしています。GUIは、行事予定の行にある項目名と解の項目名と一致するかどうか見ていて、一致したときだけ解を出力しています。これにより、今月部分に年休数等の今月解が出力されます。

今月累計=前月までの累計+今月解の値


であるので、

次月更新時に、今月累計の値を値コピーすればよい、ということになります。この処理は、敢えて手作業にしています。解は複数回出力される可能性がありますが、更新処理は、ただの1回である必要があるためです。

2025年4月27日日曜日

解集合プログラミングによるナーススケジューリング問題の解法

鍋島先生のGithubを眺めてたら、面白そうなテーマがありました。 

GitHub - nabesima/yamanashi-nsp: An ASP Encoding for Real and Artificial Nurse Scheduling Instances at Yamanashi University Hospital

論文を見たかったのですが、見当たりませんでした。山梨大学病院の実データを元にしているようです。これによるとやはり長日勤なのでしょうか?

解集合プログラミングと言えば、名古屋大学の番原先生。

で、見てみると、ありました。

看護師の勤務表作成 ー解集合プログラミングの応用ー | 情報玉手箱

そうなんですね。

私は、昨今のChatGPTや、DEEPSEEKによるモデリングを想定しているのですが、別なアプローチのようです。

こちらの方は、中国勢が支配しているようですが、未だ未だ難しいと思っています。

GitHub - nl4opt/nl4opt-competition: Natural Language for Optimization Modelling

ただし、

MIT Tech Review: 小規模言語モデル:世界を変える10大技術

という話もあり、目が離せない状況に変わりはありません。また、スケジュールナースの構造化Jsonは、汎用MIPモデルと違って、それほど広範囲のモデリングを必要としません。その意味で、上のような小規模言語モデルが使えれば、ローカルでFineTuningが出来るので、研究が加速する可能性があります。ともあれ、今は、未解決のナーススケジューリング問題の解決に全力を尽くしているので、取り組むのは、全ての未解決問題を解いてから、です。(もう1年にも渡って考えているのですが未だ道筋が見えません。)


夜勤を別状態(タスク)として記述する

 夜勤は、通常のシフトと同日にあるので、シフトとはせず、タスクとして記述します。シフトでこれを行おうとすると、各々夜勤あるなしの2状態のシフトが必要となります。既に30種ほどのシフトがあり、その倍近く(約60)のシフトを定義する必要があります。これでは扱いにくいし、出来れば25程度にシフトの状態数を抑える、という原則に反するので、シフトとは独立した状態のタスクを用います。


まずは、シフトを定義します。


次に夜勤があり得るシフトをフェーズで定義します。
下で定義されていないシフト、例えば年休や夜勤明けはありません。これらのシフトでは、夜勤の存在を許さないので、敢えて記述しません。


次にタスクを次のように記述します。状態としては、夜勤があるかないかの2状態だけを意味します。

以上がフレームワークになります。これから記述する制約は、全てこのフレームワーク上の制約となります。

この解は、次のようにシフト(上段)、タスク(下段)でハイブリッド形式で出力出来ます。


2025年4月26日土曜日

制約開始日設定が間違っていたらエラーを出して終了

 このプロジェクトでは、制約開始日前6日を見ることで7日間中の月曜日を検出しています。



正しく制約表示開始日が設定されていないと、その後の計算が全て狂ってしまいます。



なので、制約開始日が正しく設定されてない場合は、エラーを出して終了としています。

そのためのコードが次です。(下記以外にimport ctypesが必要です。)


今回プロジェクトでの全体ソースです。


import sc3
import ctypes

def 予定以外の今月区間の来振休を禁止():
    for person in 全スタッフ:
        for day in 今月区間:
            if shift_schedules[person][day][0] =="来月振休" or shift_schedules[person][day][0]!="":#ハード来振休はスキップ
                continue
            v=sc3.GetShiftVar(person,day,"来月振休")#それ以外は禁止
            s=staffdef[person]+daydef[day]+"来振休禁止"
            sc3.AddHard(~v,s)



def 振休今月区間の日夜勤数は第1週から第8週までの振休数に等しい(person,weeks):
    夜日リスト=[]
    振休リスト=[]
    来月残振休リスト=[]
    include_next_month_day=False
    for day in weeks:
        if day < 制約開始日:
            continue
        if day in 今月区間休日前:
            if person in 夜勤可能者:
                vt=sc3.GetTaskVar(person,day,0,"夜勤")
                夜日リスト.append(vt)
        if day in 今月区間休日:
            vs=sc3.GetShiftVar(person,day,"日勤")
            夜日リスト.append(vs)
        if day in 今月区間平日:
            v=sc3.GetShiftVar(person,day,"振休")
            振休リスト.append(v)
        if day in 来月区間平日:
            v=sc3.GetShiftVar(person,day,"来月振休")
            振休リスト.append(v)
            include_next_month_day=True

    if day in 来月区間平日:
        if day not in weeks:
            v=sc3.GetShiftVar(person,day,"来月振休")
            来月残振休リスト.append(v)
    s=  staffdef[person]+ "来月残り期間は振休なし";     
    sc3.AddHard(~sc3.Or(来月残振休リスト),s)

    offset=0
    allowable_errors=3
    st="SeqSoftComp " +staffdef[person]
    print(st,len(夜日リスト),len(振休リスト))
    if include_next_month_day:#来月を含むなら最強ソフト
        sc3.AddSoft(sc3.SoftSeqComp(offset,allowable_errors,振休リスト,夜日リスト),st,7)
        
    else:#今月なら弱め
        sc3.AddSoft(sc3.SoftSeqComp(offset,allowable_errors,振休リスト,夜日リスト),st,2)




def 週シフト継続(person, day):
    if 平日週初日の予定があったらその週シフト継続==0:#マクロが0だったら何もしない
        return
    
    #print(staffdef[person],daydef[day])
    if shift_schedules[person][day][0] !='':
        シフト=shift_schedules[person][day][0]
        tupple=shiftdef[シフト]
        if tupple[3]=='':#業務開始時刻があるシフトをシフト継続マークとする
            return
        #print(tupple[3])
        for i in range(5):#月曜から5日間見る
            D=day+i
            if D in 休日:#休日は見ない
                continue
            if shift_schedules[person][D][0] =='':#予定ブランクのときのみ制約する
                v=sc3.GetShiftVar(person,D,シフト)
                v |=sc3.GetShiftVar(person,D,'振休')
                v |=sc3.GetShiftVar(person,D,'年休')
                v |=sc3.GetShiftVar(person,D,'宅直')
                v |=sc3.GetShiftVar(person,D,'夜勤明け')
                s=staffdef[person]+daydef[D]+"シフト継続"
                sc3.AddSoft(v,s,2)

def シフト週継続():
    for person in 全スタッフ:
        for week in [第一週,第二週,第三週,第四週,第五週]:
            for day in week:
                if day in 月 and day in 平日:
                    週シフト継続(person,day)
                    break
                elif day in 火 and day in 平日:
                    週シフト継続(person,day)
                    break
                elif day in 水 and day in 平日:
                    週シフト継続(person,day)
                    break
                else:
                    print("programming error")

def shift_aggregate_analysis():
    print("hi")

def post_main():
    shift_aggregate_analysis()

if 制約開始日 !=6:
    MessageBox = ctypes.windll.user32.MessageBoxW
    res=MessageBox(HWND, '制約表示日は、制約開始日の6日前に設定してください。', '制約表示部エラー', 0x00000010)#error icon
    exit(0)
予定以外の今月区間の来振休を禁止()        
シフト週継続()
for person in 振休可能者:
    for week in [第一週から第八週]:#
        振休今月区間の日夜勤数は第1週から第8週までの振休数に等しい(person,week)
                    

2025年4月25日金曜日

月曜日基準のプロジェクト

制約開始日を含む前6日分を最初の1週間と定義します。

月曜とANDを取ることで最初の月曜日を定義します。

月曜日に1を足したのが最初の火曜日になります

最初の月曜日以降6日分のORを取ることで第1週を定義します。

 

第1週に7を足したのが第2週になります。

同様にして第6週まで定義出来ます。

しかし、第7週目にして、循環参照のエラーとなってしまいました。

循環参照がないにもかかわらず、エラーが出るのはネストレベルが10にしているバグでした。

次回リリースより10⇒1000に変更します。














2025年4月24日木曜日

変則2交代コア勤務表の個別仕様編

著作物

変則2交代勤務表

にまとめましたので、そちらをご覧ください。


 https://www.nurse-scheduling-software.com/japanese/publications/lecture_notes_for_the_specificatios.pdf


今まで経験した個別仕様を分解して実装したプロジェクト群の説明資料です。これらの組み合わせで、自職場の実装は出来るのではないか?と思います。

これで完結です。

ご質問には、TIPSで回答しています。

なお、リリース済の資料は以下になります。

変則2交代勤務表の作成操作編

コア勤務表の解説編

https://www.nurse-scheduling-software.com/japanese/publications/lecture_notes_for_basic_project_file_description.pdf

コア勤務表解説編付録

https://www.nurse-scheduling-software.com/japanese/publications/lecture_notes_for_basic_modeling.pdf

2025年4月22日火曜日

Open source化したcuOpt

⇒GitHubに上がっていました。

 GitHub - NVIDIA/cuopt: NVIDIA cuOpt is an open-source GPU-accelerated optimization engine delivering near real-time solutions for complex decision-making challenges.

⇒Mittelman Benchmarkはこちら

https://plato.asu.edu/ftp/lpfeas.html



NVIDIA Open-Sources cuOpt for Decision Optimization | NVIDIA Blog

NVidiaがCuOptを今年後半にオープンソース化するようです。これにより、前に指摘していたCupdlpがさらに改善されることになると思います。自分で改善しようと思っていたのですが、寝て待てばよいだけなので待つことにしたいと思います。

https://schedule-nurse.blogspot.com/2025/03/cupdlp.html

https://schedule-nurse.blogspot.com/2025/02/pdlp.html

HighsのIPMソルバ、コルスキー分解にもCuOptが使えるようになるとのことのようです。

Highsチームのみならず、CoinORチームと共同開発しているようなので、基盤的にも盤石と言えるでしょう。

しかし、H100のデータを見せられても、F1と市井の車のようなものです。せめてゲーム等に使われる程度のGPUでベンチマークして頂きたいと思うのは私だけではないでしょう。

ともあれ、大規模問題に関してはゲームチェンジャになるのは疑いようがないです。大規模問題のLPソルバ問題が解消されます。

https://schedule-nurse.blogspot.com/2025/01/schedulingbenchmarks.html

もう一つのボトルネックに関しては、4/Eレクチャの後、来月から再チャレンジしたいと思います。

2025年4月21日月曜日

目的関数とは何か?

 最適化問題において最小化あるいは最大化する目的を関数で表現するもの

目的関数とは、最適化問題において最小化あるいは最大化する目的を関数で表現するもので、量的変数のこと。最適化問題や需要予測の計算の中で、最大化あるいは最小化したい関数のこと。

がCopilotの回答です。

<スケジュールナースの場合>

スケジュールナースの場合は、制約に対する違反の度合いを重みで表現し、その重みに対するエラー数を乗じた値の総和(下図)ということになります。



しかし、それは一般のナーススケジューリングにおけるモデル化の一つであって、別な目的関数が必要になることもあります。

例えば、塾講師の割り当て問題のように、塾の利益を最大化するには、単価の低い塾講師をなるべく割り当てる、みたいな最小化もあります。

<どういう勤務割り当てをしたら塾経営者は一番儲かるか?>

という問題を考えてみたいと思います。塾講師の1コマあたりの単価は決まっているとします。

ここでは、講師側のQOL(Quality Of Life)は考慮せずに、経営者側だけの都合で考えます。QOLに拘わるソフト制約はなしとします。

講師のコマあたりの単価表です。安藤は、カリスマ講師で単価が高いです。




シフトは、コマ数2,3,4しか存在しません。空き時間であっても指定シフトの支払いをするものとします。なので、各シフトに対するコストを設定します。

次のPythonコードで、コスト7*3=21種類のコストIndexを設定します。


Excelで並びを整えコピーします。



貼り付けします。


総コスト65700円が最適値として求まりました。Algorithm2で1秒でした。



結果は、


予想通り、コストの安い講師に集中して割り当てされていることが分かります。当然ですね。しかし、単価が高い上位3人は、全く割り当てられることがありません。経営者の儲けだけを考えた場合のモデルなので仕方ありませんが、契約的に問題となるので、実際的には、何らかのハード制約追加が必要でしょう。

例えば、土日の単価や、夜時間があったとしても自由にコストも変えることが出来ます。

また、シフトでのコストを求めましたが、空き時間には支払いをしないコマ単位でのモデル化も可能です。

<何を最小化したいか?>

私が扱う問題の殆どは、制約の違反に対するペナルティ重みの最小化という目的関数になります。しかし、上の例のように、それが目的関数ではない場合もあります。

仕様を頂いて、仕事がスタートするのですが、何を目的関数としたいのか?が曖昧だと、開発・設計そのものが出来ません。

仕様は、願望を書くことではありません。理想を書く場所でもありません。実績に基づくデータがあって初めて仕様は書けると思います。

■必ず実現すること ハード制約

■出来れば実現したいこと ソフト制約

■目的関数が明示されていること 何を最小化(最大化)したいか?

これらがセットになって初めて仕様です。ナーススケジューリング問題の場合、目的関数は自明です。そうではない問題の場合は、何を最小化したいですか?とお尋ねすることにしています。




2025年4月19日土曜日

介護3交代サンプルプロジェクト追加

 お問い合わせを頂き追加しました。今まで、接した介護施設は、すべからく2交代16hでしたが、そのような施設もあるのかと、少し驚きました。



看護師3交代からの変更は僅かで、記述変更3分で出来ます。休日の日勤者数制約をナシにして、毎日の日勤者数に変更するだけで簡単です。祝日まで消しても5分ほどでした。


これも、3交代標準パターンというプロジェクトがあったおかげです。


変則2交代コア勤務表の解説編のアップデート

コマ数チェックプロジェクトを追加しました。


Excelシートを読み込んで、読み込み時にスタッフプロパティシートを更新しますが、その際、コマ数をチェックするという処理を入れることをお勧めしています。物理的に無理なものは無理です。

物理限界を超えなくても、境界上もしくは、境界に近いときは、求解前にそれを指摘してあげた方が、親切というものです。

スケジュールナースの場合、物理境界であっても、何気なく解が求解出来たりする場合もありますが、それは、多くの場合ブランク予定です。予定が入ると、正確な物理限界、最適化ソルバ上のLB(Lower Bound)は、求めることが非常に困難となります。その場合は、数理ソルバに依らざるをえません。

それは、ともかく実務的な予定範囲で、どのくらい物理限界でどれくらいマージンを持てばよいか?というのは、経験値に依らざるを得ません。一般的な人が作る勤務表では7%位かな?と思っています。このあたりは職場の仕様・環境により変わることになります。皆さまの職場で、知見を積み重ねて行っていただけたら、と思います。


https://www.nurse-scheduling-software.com/japanese/publications/lecture_notes_for_basic_project_file_description.pdf


2025年4月18日金曜日

Q.Excelファイルの読み込みエラーについて

Q. メニューから、「Excel取り込み出力設定」→「Excelから表を読み込む」の機能から、

以前にExcelで出力した「表」のファイルを読み込んだ際、

「インデックスが範囲を超えています。負でない値で、コレクションのサイズよりも小さくなければなりません。

パラメーター名:index」

というエラーが発生してしまいます。

先月までは、問題なく使えていたプロジェクトファイルとExcelファイルを読み込んでいるため、エラーの原因の判断が付かない状態です。

アドバイスをいただけないでしょうか。


Ans.


大変申し訳ございません。スケジュールナースが最近アップデートしたので、それに関連すると思われますが、原因が分かりません。

プロジェクトファイルとExcelシートを送付頂ければ、解析可能で何らかの対策が打てると思いますので、送って頂けるようお願いいたします。

以下、関連情報です。

A)原因として考えられるのは、バージョンアップに伴うデータ構造の齟齬です。この場合は、現在のバージョンで書いたものは、読み込めるはずです。過去のバージョンで書いたものは、読み込めません。この現象の場合は、現在のバージョンで再書き込みするほかありません。

B)現在のバージョンで書き込んだものが読めない場合は、何らかのスケジュールナースのバグの可能性があります。

C)シートを一つだけのファイルにして、読み込むと問題のシートが分かると思います。そのシートを除外して読み込むことは出来ると思います。

祝日を内閣府データから読む

 4週8休と月区間が異なるプロジェクトで自動更新用に、来月用の祝日データが必要となります。その場合、「来月」は、スケジュールナースの管理外となってしまいます。Pythonが、利用できるのは、「今月」内だけで、範囲外の内蔵祝日データを利用することが出来ません。

思案しまして、ネット上にあるデータを読み込むことにしました。スケジュールナースのPythonは組み込み用でして、備えているライブラリは限定的です。追加インストールも出来ません。そこで、組み込み済み範囲内のライブラリを使用して実装することになります。

参考にしたのは、


内閣府が提供する祝日・休日情報の csv を JSON フォーマットで出力する Python スクリプト · GitHub

このデータは、現在2025と2026までのデータしかないです。既に多くのソフトウェアがこのCSVを参照していると思うので、更新もこのCSV上で行って欲しいものです。


実装したソースは次です。


2025以前のデータは不要なので捨てています。土日にかかる祝もカウントされないので捨てています。残ったカウントを週休8にプラスしたのが目的の週休数になります。

2025年4月17日木曜日

4週8休区間と今月区間テンプレートの設計pythonの実装解説

 Pythonポスト処理は、求解とは独立したプロジェクトです。post_main()が、ポスト処理用としてCallされるmain()になります。

そこで、Excelを呼び出しています。Excelそのものを呼び出すのでExcelがないとエラーとなります。


ここで、悩ましいのが、何らかの処理でエラーするとExcelそのものがプロセスとして残ってしまい、後のExcelオペレーションでエラーになる悪さをすることです。このあたりまでちゃんと実装するには、次のようにする必要があります。

pywin32をExcel操作にサッと使う時のテンプレート #Python - Qiita


次が実質的なmain()になります。


来月の期間設定では、期間の設定のExcelシートを読み取るかどうかのダイアログを出力します。出力しないのならば、そこで終了です。

A)次に特定の日の出力処理を行います。特定の日の作成は、3つのリストを作成することによって行います。

B)次に、確定した特定の日リストを使って期間の設定処理を行います。

A)B)の書き込みを行います。

次にスタッフプロパティシートの処理書き込みを行って終了です。

2025年4月15日火曜日

4週8休区間と今月区間テンプレートの設計その1

 

今までなかったのですが、設計してみました。
公立病院/大学病院では、3交代⇒変則2交代に移行しても、意味不明なこの規則だけは生き続けているようです。(移行時になんとかならなかったか?と思います。)

使用実績はないです。何かの参考になれば幸いです。











2025年4月14日月曜日

Q.初めてスケジュールナースを使って、5月の勤務表を完成させての感想

 以下、今回初めてスケジュールナースを使って、5月の勤務表を完成させての感想です。

 制約がまだ把握できていないので使い切ることはできませんでした。少しづつ勉強したいと思います。

 解が出ないときにどの制約から緩めていくかは経験を積まないといけないと思いました。

 今回病欠や産休などがあり勤務組むのが難しくなってきましたので対策を考えねばと思っています。

Ans.

スケジュールナースは、初めての方が使いこなすのは容易ではありません。

何でもできる代償といいますか、「簡単」の裏返しといいますか、ともかく取り扱っている対象そのものが、ナーススケジューリング問題という複雑で精緻な組み合わせの上に成り立っており、針の穴をも通すようなことを要求しています。スケジュールナースが難しいのではなく、問題そのものが難しいのです。これに真正面から取り組み、管理コントロールしていくのは容易なことではありません。


もし解がない、等でお困りであれば、その都度、症状とどうしたいかをプロジェクトファイルを添えて送っていただければ、お教えします。

また、買い切り版の1年間サポート付をご購入頂いたお客さまには、その都度、ZOOMでお客さま画面を共有しながら操作・お教えしております。

困ったときは、遠慮なくご相談ください。

そういったことを積み重ねれば、必ず使いこなせるようになります。思い通りにコントロールできるようになったとき、大きな安心が得られます。あせらずじっくりと進めてください。

2025年4月13日日曜日

4月EndRelease 改善その9

 列制約の整数計数オプション追加





半人前の実装用です。従来Pythonでしか記述できませんでしたが、GUIで半人前の実装をするときに使います。




2025年4月12日土曜日

Q「フォルダが開きません」

 Q.5月の勤務表を作成中です。3月末頃からスケジュールナースで操作をしていました。4月になり、保存していたファイルがパソコンにより開けなくなりました。作業していたパソコンでは開けているので作成はできています。

別のパソコンで開くと以下の画面が出ます。


過去に作成したプロジェクトは開くことが出来ます。

アップデートが関係しているのかと思っているのですが、改善方法がわかりません。ご教授お願いします。

Ans.
原因は、スケジュールナースがバージョンアップされたためです。
スケジュールナースのソフトウェアは、自動更新されますが、書き込んだ方のPCが更新され、読み込んだ方のPCが未だ更新されていないと、ご指摘の現象となります。

時間が経てば、自動更新され解消しますが、お急ぎの場合は、手動で更新する必要があります。
その場合、

■サブスクの場合:
サブスクのサイトに行って手動で更新してください。

■買い切り版の場合:
インストールマニュアルに手動アップデートの方法が記載されています。それに従って手動アップデートしてください。


2025年4月11日金曜日

4月EndRelease 改善その8

自動シフトチェックなしのソフト制約挙動変更

変更前:

 自動シフトチェックがされていないシフトが割り当てられることがありました。

変更後:

 「または(非自動シフト含む)」は、同じ挙動です。自動シフトチェックがされていないシフトが割り当てられることがあります。

それ以外のラベルで、自動シフトチェックがされていないシフトが割り当てられることはありません。(スタッフ毎の設定で「ソフト制約を優先しない」をチェック時に限ります)






2025年4月10日木曜日

4月EndRelease 改善その7

 Excel Format出力の改善です。新たにオプション「行制約」を追加しました。目印になるシンボルを記述すると、そこが行制約項目行となります。行制約項目に出力された項目はUserFormat出力時に出力することが出来ます。(注意:スケジュールナース側で出力されていないと出ません。)

下の例では、行事予定の行に項目が記されています。その列に対してExcelへデータ出力します。


これには、次のオプションで指定します。

応用としては、たとえば、累計を取るときに使用できると思います。

(指定項目を指定位置にデータ出力できるので、Excelでデータを取得し易い)