2025年1月6日月曜日

スケジュールナースを使いこなすのに必要なのは、国語力

 正月に、親戚の高専生がゲームプログラマを目指している、というのを聞いて、老婆心ながら、「プログラマになるなら国語力が大事だよ。」

と言ってしまいました。そう言っているのは、私だけではないようで、

プログラミング教育にはどうして国語が必要と言われるのか? | ティーチャーズメディア

国語力とプログラミング力の関係 解説編:【写真】天才プログラマに聞く10の質問(4)(1/2 ページ) - @IT

スケジュールナースを使う上においても同じことが言えると思っています。スケジュールナースは、プログラムじゃないじゃん、と言われるとその通りですが、

実は、皆さんは、制約プログラムをGUIで書いています。コンピュータに指示するというの、GUIではありますが、言葉で指示をしていることに同じです。言葉なしでは、一切動きません。言葉という指示があって、スケジュールナースは、動きます。

仕様を人に伝える、にしてもそうです。ユーザさんの中で、お医者さんやら看護師勤務表を作っておられる方がいらっしゃいます。この方は、現役ではないのですが、いわゆる事務屋さん、だと思います。そういう方が、畑違いの仕様をまとめて提示してくれていて、非常に助かっています。で、その力の源泉は国語力であると思う次第です。使いこなすのに、ITエンジニアである必要はない、という証明だと思います。

プログラムでも、最初からコーディングすることはありません。何を実現するかというのは、企画書や仕様という言葉で形にすることで明示することから始まります。

このユーザさんのように、事務または事務長さんであるユーザさんは結構多いと思います。人と人とのコミュニケーションに長けているということ、人の話を聞くこと、理解してまとめること、これは、全て国語力です。なので、別に看護師長である必要はない、ということだろうと思います。勿論、看護師長自身が使いこなせれば、それがベストですが、制約をメンテナンスする人が、看護師長である必要はありません。

男性か女性かという点においては、経験上、男性の方が国語力の優位性が高いと思います。何故かは分かりません。男性の方がロジカルである傾向があるからかもしれません。

女性がビジネスで活躍するには「ロジカルシンキング」をもっと学ぶべき?(横山信弘) - エキスパート - Yahoo!ニュース

性別を問わず、どんなにITリテラシーに疎くても、国語力とやる気さえあれば、サポート出来る自信はあります。言葉のコミュニケーションさえできれば、必ず前に進めることが出来るからです。

ちなみに、今までに出会ったなかで、国語の達人と思うのは、弁理士さんです。特許の請求項をクレームと言うのですが、クレームでは、日本語で論理的なことを漏れなく明確に記載する必要があります。私が書いたクレーム原案は、殆ど跡形もなく添削されて別なクレームになるのが普通です。それは、特許クレームという一見複雑怪奇な文章ではありますが、見事に精緻で一分の隙もない文にする日本刀の研師のような作業です。これを成すには国語力、ということをおっしゃっていたのを思い出しました。

その弁理士さんは、推敲の際、句読点を区切りながらクレームをよく音読していました。正しいかどうか常に反芻しながら頭を回転させていたのでしょう。で、

スケジュールナースで制約が思い通りに動かないとき、制約を音読してみる、

というのをお勧めしています。自分が今書いた制約が、確かに意図通りであるか?反芻することで、バグを発見できるかもしれないからです。

2025年1月5日日曜日

従前の入力スタイルによる求解の仕方

 まずは、ブランク予定で、UB=0を確認しておきます。

UB=0になっています。


スタッフの希望や、決まった予定を入力します。入力したらロックします。


全部ロック します。


ロックした部分は、黄色になります。


求解してUB=0になっていることがベストです。UB=0とならなくても、許容範囲であるかどうかが、ポイントになります。許容範囲でないとすれば、予定を変更する必要があるということですが、それはまた別の機会に。許容範囲内であると仮定して話を進めます。


さらに、自分が良いと思う勤務を入力していきます。黄色部以外が入力したところです。


途中、途中で求解します。

その都度に、解が存在し、そのベスト解が提示されます。それは、これ以降人間が如何に入力しようとも、それ以上に目的関数値は、変わることはない、ということです。それが、気にいらなければ、


取り消し、やりなおしにより、入力途中まで戻って再トライします。以上が従前スタイルによる勤務表作成の仕方です。

同じスタイルではありますが、違いは、解が存在する保証があること、そしてベスト解よりも良い解(目的関数値が低い)が存在しないことが違います。自分が入力した勤務パターンでは、其のあとにどんなに頑張っても相応のエラーとなってしまう将来が示されることです。

この違いは、大きいと思います。安心できるのでは、ないでしょうか?





2025年1月4日土曜日

Q.余剰分をなるべく均等に他の勤務日に割り振りする 条件設定は可能でしょうか? 続きの実装

 Ans.

前回設定では、無理やり平準化を行う方法を示しました。この方法は、本来、勤務表作成の最後に行う類のものです。初期設計時、ブランク予定で行うべきものではありません。

原則的に、ブランク予定では、エラー0を基準に制約設計を行います。前回のままでは、その原則から外れているので、正しい制約の仕方をここで述べたいと思います。

まず、予定なしでのコマ数から、人員とプロジェクト月の平日日勤者のコマ数を自動算出してみましょう。

Excelで行ってもよいですし、スケジュールナースのマクロで行ってもよいです。

下記でC(x)は、x集合の要素をカウントする関数です。

最初に、全スタッフの人数や、今月の日数を求めています。









残ったコマ数を平日数で割れば、平均の日勤者数となります。

rounddown(x,0)で、floorの働きを行います
roundup(x,0)で、ceilの働きを行います。

マクロで計算した値を最大値にしています。
解は、5-6名で分布しています。

これで、任意のスタッフ数、任意のプロジェクト月に対して自動設定することが出来ました。エラーは0です。

<実際には、週休・祝休以外の休みがある>

実際には、夏休み、有給等の休みがあるでしょう。上記実装の問題は、それを無視した値となっています。その辺を加味して設定する必要があります。

<その他の休みまで含んで自動制約化するには?>

Pythonでの記述になります。一応の実装を示します。(制約設計時にここまで凝ることは、全くお勧めしません。その他要因、増員日やらその他要求があるのが普通でそのたびに、Pythonも影響を受け修正を余儀なくされます。従って、自動化実装は、十分な時間が経過した安定稼働後に、自動化したい要求があるときに検討すればよいと思います。)

予定で入れたその他予定休みを勘案して、下限と上限を自動計算する例です。

次のような、週休・祝休以外の休み予定が入ったとします。


Pythonで記述する制約のチェックを外します。

Pyhonでその他休みの総和を求め、マクロでのコマ数計算値を呼び出して、平日残りコマ数最小から引き算、平均の平日日勤者数を求めます。

後は、範囲の整数を求めて、最大最小を制約しています。


import sc3
import math

def is_off(s):
    if s=='有休' or s=='夏' or s=='特休' or s=='休日':
        return True
    return False

#その他休み総和を求める
other_off_days_count=0
for person in 全スタッフ:
    for day in 今月:
        if is_off(shift_schedules[person][day][0]) :
            other_off_days_count+=1

r=平日残りコマ数最小-other_off_days_count
ravg=r/平日数
その他休みを考慮した平日残りコマ数最小=int(math.floor(ravg))
その他休みを考慮した平日残りコマ数最大=int(math.ceil(ravg))
print("\n\nその他休みを考慮した平日残りコマ数",r,"平均日勤者数=",ravg,"最小=",その他休みを考慮した平日残りコマ数最小,"最大=",その他休みを考慮した平日残りコマ数最大)

#GUI制約をDisable
#sc3.ConstraintEnable('列制約全体.平日日勤者リーダ看護師長除く4名から5名',False)

#今月平日の日勤者数を制約する

for day in 今月平日:
    vlist=[]
    s='言語その他休みを考慮した平日日勤者数'+' '+daydef[day]+'\n'
    allowable_errors=3
    for person in 全スタッフ:
        v=sc3.GetShiftVar(person,day,'日勤')
        vlist.append(v)
    sc3.AddSoft(sc3.SeqError(その他休みを考慮した平日残りコマ数最小,その他休みを考慮した平日残りコマ数最大,allowable_errors,vlist),s,4) #min max allowable errors list
 
適当にランダム入力でその他休みを入力してみました。

GUI制約は、チェックを外します。

解は、2-3人範囲となりました。本来は、下限人数が決まっていますが、今回は、検証目的であり、浮動小数範囲含む整数範囲に制約しています。

今度は、予定クリアしてブランクにしてみます。

すると、5-6名で自動的に制約が変更されて、そのようになっています。


以下はログです。両方共エラー0です。
コンパイルの準備中 ソルバを呼び出し中です。 制約をコンパイル中です。 Python プロパティファイルの生成が終わりました。 その他休みを考慮した平日残りコマ数 100 平均日勤者数= 5.555555555555555 最小= 5 最大= 6 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. Algorithm 1 Solving Process Started.. o 1493 3.962000(sec) o 1481 4.276000(sec) o 1459 4.401000(sec) o 1435 4.446000(sec) o 1397 4.481000(sec) o 1387 4.497000(sec) o 1293 4.513000(sec) o 1284 4.545000(sec) o 1240 4.576000(sec) o 1215 4.635000(sec) o 1165 4.674000(sec) o 1150 4.690000(sec) o 1120 4.746000(sec) o 1098 5.504000(sec) o 1095 5.692000(sec) o 1076 5.723000(sec) o 1072 6.032000(sec) o 1070 9.859000(sec) o 1064 9.888000(sec) o 1052 9.906000(sec) o 1046 9.924000(sec) o 1037 9.945000(sec) o 1004 9.994000(sec) o 976 10.016000(sec) o 968 10.060000(sec) o 946 10.098000(sec) o 936 10.114000(sec) o 920 10.184000(sec) o 909 10.216000(sec) o 896 10.230000(sec) o 875 10.244000(sec) o 871 10.323000(sec) o 865 10.339000(sec) o 850 10.354000(sec) o 845 10.378000(sec) o 831 10.417000(sec) o 820 10.464000(sec) o 789 10.547000(sec) o 776 10.564000(sec) o 773 10.581000(sec) o 768 10.619000(sec) o 763 10.649000(sec) o 759 10.669000(sec) o 736 10.685000(sec) o 713 10.756000(sec) o 706 10.821000(sec) o 693 10.978000(sec) o 675 10.994000(sec) o 666 11.019000(sec) o 662 11.035000(sec) o 659 11.052000(sec) o 656 11.066000(sec) o 651 11.079000(sec) o 645 11.124000(sec) o 632 11.148000(sec) o 597 11.189000(sec) o 580 11.225000(sec) o 575 11.239000(sec) o 558 11.410000(sec) o 547 11.482000(sec) o 544 11.555000(sec) o 511 11.791000(sec) o 507 11.858000(sec) o 488 11.938000(sec) o 474 12.023000(sec) o 455 15.734000(sec) o 452 15.769000(sec) o 422 15.788000(sec) o 388 15.838000(sec) o 371 15.903000(sec) o 343 15.967000(sec) o 313 16.042000(sec) o 294 16.061000(sec) o 285 16.076000(sec) o 269 16.101000(sec) o 261 16.117000(sec) o 258 16.131000(sec) o 236 16.233000(sec) o 228 16.247000(sec) o 204 16.287000(sec) o 174 16.480000(sec) o 172 16.553000(sec) o 155 16.624000(sec) o 152 16.640000(sec) o 141 16.716000(sec) o 108 16.793000(sec) o 102 16.847000(sec) o 92 16.879000(sec) o 69 17.026000(sec) o 67 17.047000(sec) o 64 17.062000(sec) o 61 17.075000(sec) o 60 17.158000(sec) o 56 21.590000(sec) o 54 21.611000(sec) o 53 21.625000(sec) o 46 21.712000(sec) o 45 21.947000(sec) o 43 21.995000(sec) o 40 22.053000(sec) o 39 22.124000(sec) o 32 22.172000(sec) o 28 22.216000(sec) o 24 22.315000(sec) o 23 22.376000(sec) o 22 22.414000(sec) o 21 22.478000(sec) o 20 22.499000(sec) o 19 22.521000(sec) o 16 22.549000(sec) o 15 22.577000(sec) o 14 22.602000(sec) o 12 22.835000(sec) o 11 22.960000(sec) o 9 22.996000(sec) o 8 23.019000(sec) o 6 23.064000(sec) o 5 23.484000(sec) o 4 23.540000(sec) o 3 23.637000(sec) o 2 23.696000(sec) o 0 23.843000(sec) Python プロパティファイルの生成が終わりました。 _____________________________________ | | | | | Weight | Errors | Cost | |___________|___________|_____________| | | | | | 7 | 0 | 0 | | 5 | 0 | 0 | | 3 | 0 | 0 | | 1 | 0 | 0 | |___________|___________|_____________| | | | | Total | 0 | |_______________________|_____________| *********UB=0(0) 25.283(cpu sec) o 0(0) 解探索が終了しました。 26 (秒) 解が得られました。 コンパイルの準備中 ソルバを呼び出し中です。 制約をコンパイル中です。 Python プロパティファイルの生成が終わりました。 その他休みを考慮した平日残りコマ数 51 平均日勤者数= 2.8333333333333335 最小= 2 最大= 3 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1日勤二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1入り二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. 禁止ペア1明け二人不可:column seq erased. Algorithm 1 Solving Process Started.. o 1543 3.482000(sec) o 1506 3.568000(sec) o 1474 3.592000(sec) o 1436 3.604000(sec) o 1413 3.617000(sec) o 1406 3.629000(sec) o 1385 3.641000(sec) o 1379 3.661000(sec) o 1363 3.672000(sec) o 1320 3.690000(sec) o 1255 3.703000(sec) o 1229 3.717000(sec) o 1216 3.728000(sec) o 1185 3.746000(sec) o 1167 3.759000(sec) o 1141 3.865000(sec) o 1129 3.877000(sec) o 1124 3.888000(sec) o 1079 6.724000(sec) o 1073 6.777000(sec) o 1046 6.789000(sec) o 1028 6.806000(sec) o 1011 6.820000(sec) o 998 6.831000(sec) o 973 6.842000(sec) o 972 6.853000(sec) o 971 6.922000(sec) o 946 6.944000(sec) o 916 6.993000(sec) o 898 7.008000(sec) o 873 7.050000(sec) o 864 7.062000(sec) o 853 7.074000(sec) o 816 7.120000(sec) o 804 7.136000(sec) o 791 7.150000(sec) o 776 7.165000(sec) o 758 7.176000(sec) o 741 7.190000(sec) o 718 7.218000(sec) o 704 7.264000(sec) o 692 7.279000(sec) o 682 7.292000(sec) o 669 7.323000(sec) o 662 7.344000(sec) o 645 7.357000(sec) o 639 7.369000(sec) o 637 7.382000(sec) o 628 7.453000(sec) o 623 7.478000(sec) o 608 7.491000(sec) o 605 7.503000(sec) o 597 7.514000(sec) o 575 7.580000(sec) o 571 7.591000(sec) o 565 7.603000(sec) o 553 7.618000(sec) o 550 7.641000(sec) o 536 7.653000(sec) o 533 7.665000(sec) o 527 7.677000(sec) o 513 7.691000(sec) o 500 7.707000(sec) o 485 10.543000(sec) o 473 10.713000(sec) o 468 10.733000(sec) o 457 10.793000(sec) o 434 10.846000(sec) o 413 10.904000(sec) o 410 10.915000(sec) o 371 10.928000(sec) o 348 10.954000(sec) o 315 10.996000(sec) o 312 11.008000(sec) o 287 11.024000(sec) o 279 11.051000(sec) o 254 11.120000(sec) o 218 11.141000(sec) o 193 11.155000(sec) o 188 11.199000(sec) o 167 11.230000(sec) o 138 11.327000(sec) o 118 11.357000(sec) o 111 11.400000(sec) o 95 11.414000(sec) o 93 11.426000(sec) o 92 11.441000(sec) o 80 11.500000(sec) o 76 15.077000(sec) o 70 15.096000(sec) o 67 15.109000(sec) o 55 15.162000(sec) o 54 15.174000(sec) o 52 15.189000(sec) o 48 15.203000(sec) o 45 15.216000(sec) o 42 15.228000(sec) o 39 15.254000(sec) o 37 15.416000(sec) o 36 15.481000(sec) o 34 15.497000(sec) o 29 15.583000(sec) o 28 15.597000(sec) o 25 15.623000(sec) o 24 15.637000(sec) o 23 15.787000(sec) o 22 15.911000(sec) o 21 15.923000(sec) o 17 16.082000(sec) o 15 16.097000(sec) o 13 16.128000(sec) o 11 16.146000(sec) o 10 16.160000(sec) o 9 16.234000(sec) o 8 16.249000(sec) o 7 16.262000(sec) o 3 16.330000(sec) o 2 16.741000(sec) o 1 17.124000(sec) Python プロパティファイルの生成が終わりました。 o 0 17.421000(sec) _____________________________________ | | | | | Weight | Errors | Cost | |___________|___________|_____________| | | | | | 7 | 0 | 0 | | 5 | 0 | 0 | | 3 | 0 | 0 | | 1 | 0 | 0 | |___________|___________|_____________| | | | | Total | 0 | |_______________________|_____________| *********UB=0(0) 18.54(cpu sec) o 0(0) 解探索が終了しました。 19 (秒) 解が得られました。

2025年1月3日金曜日

INRC2プロジェクトファイルのバグ

<現象>

 コンペティションの週毎のシフトオフフォーマットの中で、例えば、次のような記述がある場合があり、

その場合、正しく問題を解釈することが出来ません。

NU_27 Day Sat 

NU_27 Late Sat 


CT_57 Early Tue 

CT_57 Late Tue


同一日に、同一の人が二つ以上のシフトをオフにする、という仕様です。この場合、GUIでは、一つのペナルティしか与えることが出来ないので、正しく解釈することが出来ません。

正しくは、

NU_27の土曜日がDayシフトでないのならばペナルティ10

NU_27の土曜日がLateシフトでないのならばペナルティ10

と独立にペナルティを与えないといけないのですが、GUIだけでは正しく実装出来ません。

仕方ないので、GUIでは、一つのペナルティを与え、Pythonで残りをカバーするという実装にしました。

<改善後の実装>

n080w4

REQUIREMENTS
Early HeadNurse (1,1) (1,1) (0,0) (1,1) (1,1) (1,1) (0,1)
Early Nurse (1,1) (1,1) (1,2) (2,2) (1,2) (1,1) (1,1)
Early Caretaker (5,7) (5,7) (4,9) (4,8) (3,7) (3,4) (1,2)
Early Trainee (1,2) (2,3) (1,2) (2,2) (2,2) (0,1) (1,2)
Day HeadNurse (2,2) (1,1) (0,0) (1,1) (0,0) (1,1) (1,1)
Day Nurse (2,2) (2,2) (1,1) (1,1) (2,2) (1,1) (1,1)
Day Caretaker (5,8) (3,6) (4,7) (4,7) (4,7) (3,4) (3,3)
Day Trainee (2,3) (1,3) (1,4) (2,2) (2,2) (0,0) (0,1)
Late HeadNurse (0,0) (1,1) (1,1) (1,1) (1,1) (0,1) (1,1)
Late Nurse (1,1) (2,2) (2,2) (2,2) (1,2) (1,1) (1,1)
Late Caretaker (6,8) (4,6) (6,8) (3,6) (5,7) (2,3) (2,3)
Late Trainee (2,3) (1,2) (1,3) (2,3) (0,3) (1,1) (0,0)
Night HeadNurse (0,1) (0,0) (1,1) (1,1) (1,1) (1,1) (1,1)
Night Nurse (1,1) (1,1) (1,2) (2,2) (2,2) (1,1) (1,1)
Night Caretaker (4,7) (5,7) (5,10) (5,8) (3,7) (3,3) (3,4)
Night Trainee (2,3) (2,2) (1,3) (2,4) (2,3) (1,2) (1,1)

SHIFT_OFF_REQUESTS = 67
CT_37 Night Mon 
CT_42 Night Mon 
TR_73 Any Mon 
CT_31 Any Tue 
CT_35 Late Tue 
CT_45 Any Tue 
CT_47 Early Tue 
CT_50 Any Tue 
CT_51 Any Tue 
CT_52 Any Tue 
CT_54 Any Tue 
CT_57 Any Tue 
HN_5 Late Tue 
HN_6 Any Tue 
NU_12 Late Tue 
TR_61 Any Tue 
CT_44 Any Wed 
CT_53 Late Wed 
CT_54 Early Wed 
HN_3 Early Wed 
NU_17 Any Wed 
TR_75 Any Wed 
TR_78 Late Wed 
CT_26 Late Thu 
CT_42 Night Thu 
CT_44 Any Thu 
CT_48 Any Thu 
CT_50 Any Thu 
CT_56 Any Thu 
CT_57 Day Thu 
NU_12 Day Thu 
TR_65 Any Thu 
TR_66 Any Thu 
TR_68 Any Thu 
TR_69 Day Thu 
TR_72 Early Thu 
TR_74 Early Thu 
TR_75 Any Thu 
CT_27 Any Fri 
CT_30 Any Fri 
CT_39 Night Fri 
CT_41 Any Fri 
CT_47 Any Fri 
HN_4 Any Fri 
HN_5 Day Fri 
NU_19 Any Fri 
TR_64 Day Fri 
TR_64 Night Fri 
TR_67 Any Fri 
CT_21 Any Sat 
CT_23 Any Sat 
CT_30 Any Sat 
CT_32 Any Sat 
CT_42 Any Sat 
CT_45 Any Sat 
CT_48 Any Sat 
HN_1 Any Sat 
NU_15 Late Sat 
TR_68 Any Sat 
TR_76 Any Sat 
CT_31 Day Sun 
CT_59 Late Sun 
HN_2 Late Sun 
HN_7 Any Sun 
HN_9 Any Sun 
TR_66 Day Sun 
TR_68 Any Sun
 
これに対して、次のようなGUIとPython記述になります。

INRC2 8weeksの未解決問題を全て解いてからプロジェクトファイルはアップロードします。。

2025年1月2日木曜日

INRC2用CSVコンバータバグFIX

INRC2プロジェクトのcsv解をValidator用のフォーマットに変更するプロジェクトcsv.nurse3にバグがありました。
下記が修正後のソースです。
import win32gui, win32con, os
import csv

def get_day_str(day):
    if day==0:
        return "Mon"
    elif day==1:
        return "Tue"
    elif day==2:
        return "Wed"
    elif day==3:
        return "Thu"
    elif day==4:
        return "Fri"
    elif day==5:
        return "Sat"
    elif day==6:
        return "Sun"
    else:
        raise ValueError("Unsupported Day!")

def get_shift_name(label):
    if label=='D':
        return "Day"
    elif label=='N':
        return "Night"
    elif label=='L':
        return "Late"
    elif label=='E':
        return "Early"
    else :
        raise ValueError("Unsupported Shift!")

def get_task_name(label):
    if label=='He':
        return "HeadNurse"
    elif label=='Nu':
        return "Nurse"
    elif label=='Ca':
        return "Caretaker"
    elif label=='Tr':
        return "Trainee"
    else :
        raise ValueError("Unsupported Shift!")


def make_weeks_data(lst,fname):
    persons=int((len(lst)-3)/2)
    items=len(lst[0])
    print('items=',items)
    name=""
    phases=3
    weeks=4
    
    start_row=2
    print('persons=',persons)
    if items==107:
        phases=3
        weeks=4
    elif items==254:
        phases=4
        weeks=8
    elif items==142:
        phases=4
        weeks=4
    else:
        raise ValueError("Unsupported Items!")
    
    start_day=2+phases*7

    for week in range(weeks):
        w_str=""
        file_name = "sol-week" +   str(week) + ".txt";
        assigned=0

        for person in range(persons):
            #print(person)
            #print(lst[person*2+2][0])
            name=lst[person*2+start_row][0]
            print(name)
            for day in range(7):
                shift=lst[person*2+start_row][week*7*phases+day*phases+start_day]
                if shift=='O' or shift=='':
                    continue
                for ph in range(phases):
                    task=lst[person*2+1+start_row][week*7*phases+day*phases+start_day+ph]
                    if task !='':
                        w_str+=name +' '+get_day_str(day)+' '+get_shift_name(shift) +' '+get_task_name(task)+'\n'
                        assigned +=1#DEC252024 Bug Fix
                        break
                #assigned +=1

        Wstr="SOLUTION\n";
        Wstr+=str(week)+' '+ fname[0:6]+ "\n\n";
        Wstr+="ASSIGNMENTS = "+ str(assigned) +"\n";
        Wstr+=w_str
        f1 = open(file_name, 'w')
        f1.write(Wstr)
        f1.close()
        print(file_name,Wstr)

        

def post_main_sub():

#    try:
        filter='csv\0*.csv\0'
        customfilter='Other file types\0*.*\0'
        fname, customfilter, flags=win32gui.GetOpenFileNameW(
        InitialDir=project_file_path, #os.environ['temp'],
        Flags=win32con.OFN_ALLOWMULTISELECT|win32con.OFN_EXPLORER,
        File='somefilename', DefExt='csv',
        Title='GetOpenFileNameW',
        Filter=filter,
        CustomFilter=customfilter,
        FilterIndex=0)
        print ('open file names:', repr(fname))
        print(fname)
        
        with open(fname, encoding="utf-8") as f:
            lst = list(csv.reader(f))
            print("row length=",len(lst))
            #basename = os.path.basename(fname)
            basename_without_ext = os.path.splitext(os.path.basename(fname))[0]
            make_weeks_data(lst,basename_without_ext)
            

def post_main():
    post_main_sub()

 

2025年1月1日水曜日

Q.全く動かないのですが。

 Ans.

ナーススケジューリング問題は、精緻な制約の積み重ねで成り立っています。新しくプロジェクトを始める場合は、ステップバイステップ、一つ制約を書いて確認、一つ制約を書いて確認...をしながら構築していきましょう。大事なことなので、もう一度書きます。一つ制約を書いて確認、一つ制約を書いて確認.



次は、頂いたプロジェクトの状態です。赤のメッセージがあるので、「解がない」状態であることが分かります。


次のようなハード制約矛盾の要因がヒントとして表示されます。(スタッフ名は削除していますが、実際には、表示されています。)

●次の組み合わせが充足していません。

● 列制約全体.長日勤は4人 2024-12-29

● 列制約全体.入りは4人 2024-12-14

● 列制約全体.入りは4人 2024-12-21

● 列制約全体.明けは4人 2024-12-15

● 列制約全体.明けは4人 2024-12-22

● 列制約全体.平日日勤者5名以上 2024-12-11

● 列制約全体.平日日勤者5名以上 2024-12-12

● 列制約全体.平日日勤者5名以上 2024-12-17

● 列制約全体.休日日勤者3名以上 2024-12-28

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.休日・夏季・正月休暇回数 

● 平準化絶対的制約.入り回数 

● 行制約同数カウントグループ2.長日勤=入り 

● 行制約同数カウントグループ2.長日勤=入り 

● 行制約同数カウントグループ2.長日勤=入り 

● 行制約同数カウントグループ2.長日勤=入り 

● 行制約同数カウントグループ2.長日勤=入り 

● 行制約同数カウントグループ2.長日勤=入り 

● 行制約同数カウントグループ2.長日勤=入り 

● 行制約同数カウントグループ2.長日勤=入り 

これを見ると、特定の日(例えば1月3日)、特定のスタッフでエラーが出ているのではなく、広く分布していることが分かります。

このような状態になった場合、最初に行うことは、予定の影響を取り除く(ブランクにする)ことです。
しかし、今回予定は既にブランクです。それが意味するのは、制約設計そのものに問題があるということです。(菅原システムズが提供するプロジェクトファイルでは、予定なしではエラー0に設計を基準としています。今回、お客さまご自身の設計です。)

次に行うことは、制約グループのチェックをオフにしてみます。

具体的には、列制約グループのチェックをオフにして求解してみます。


すると、解が直ぐに出ました。
列制約内にハード制約が矛盾要因となっていることが分かりました。
次に、列制約内に何の制約をオンにすると問題が生じるかということを見ます。
具体的には、再度列制約グループのチェックをオンにし夜勤を全てオフにしてみます。
解はありました。
今度は、夜勤をオンにし、日勤をオフにしてみます。
次のようなエラーとなりました。
●次の組み合わせが充足していません。
● 平準化絶対的制約.入り回数 
● 平準化絶対的制約.入り回数 
● 平準化絶対的制約.入り回数 
● 平準化絶対的制約.入り回数 
● 平準化絶対的制約.入り回数 
● 平準化絶対的制約.入り回数 
● 平準化絶対的制約.入り回数 
● 平準化絶対的制約.入り回数 
● 平準化絶対的制約.入り回数 

部分をダブルクリックして当該制約に飛びます。ハード制約になっていることが分かります。

以上の現象を、整理してみると、

■列制約夜勤関係
■スタッフの入り回数=行制約

のハード制約間に矛盾が生じていそうだ、ということが分かります。

解がないのは、ハード制約間の矛盾なので、どちらかあるいは両方をソフト制約にすれば、解があるはずです。


今、入り回数をソフト制約レベル1にソフト制約化してみます。

新しくレベル1を設定したので、行制約レベル1にチェックを入れ求解します。
解は、ありました。
入り回数部に確かにエラーはあり、全てチームAで発生しています。


入り回数は、スタッフプロパティシートで設定しています。




解のソフトエラー部(黄色)は、最小夜勤回数を満たしていません。エラーは、全て設定夜勤回数を下回っています。設定夜勤回数を下回っているということは、人余り方向のエラーであるということです。今回Aチームのみ発生しているので、原因は、

■夜勤回数設定に対してAチーム人余り方向のハード制約違反が生じたため

と結論づけられます。このエラーを解消するには、

■スタッフプロパティシート設定回数の見直し
■夜勤入り回数のソフト制約化
■所属チーム変更(A・Bチーム間で偏りがある)

等、が考えられます。

制約を一からステップバイステップで確認しながら作る、

という原則を守って設計を進めていけば、上のような面倒な事態になることはありません。
また、コマ数計算を行っておくこともお勧めです。夜勤回数の適切な範囲が自ずと分かるでしょう。急がば廻れです。