ナーススケジューリング
Published:
By nobCategory: Posts
Tags: 数理最適化 Python-MIP Python
ナーススケジューリング問題を検索してみると論文が見つかった。
統計数理 第53巻 第2号 ナース・スケジューリング - 調査・モデル化・アルゴリズム -
東京女子医科大学付属病院の1996年11月の勤務表作成条件が記載されている。この条件を基に勤務表を作成する。
import pandas as pd
from IPython.display import display
pd.options.display.max_columns = 40
pd.options.display.max_rows = 100
シフト拘束条件
日勤のシフト拘束条件
- 毎日の日勤について, 各チームからベテラン(skilled)もしくは2年目(second-year)ナースが少なくとも2名(a_ss_day_lower_bound, b_ss_day_lower_bound, c_ss_day_lower_bound), 全体でベテランが少なくとも1名(s_day_lower_bound)必要
- 通常のウィークデイは, 合計で10-11名, 各チーム3-4名必要
- 日曜日と祝祭日(4日と 23日? )には合計9名, 各チーム3名ずつ必要
- 6日と19日は, 合計12-14名, 各チーム4-5名必要
- 26日は, 合計12-16名, 各チーム4-6名必要
日勤のシフト拘束条件(?)
- 土曜日は合計で9-10名, 各チーム3-4名必要
夜勤のシフト拘束条件
- 毎日の夜勤に働くナースの合計数は4名(all_night_lower_bound)必要
- A, B, C各チームからベテラン(skilled)もしくは2年目(second-year)ナースが少なくとも1名(a_ss_night_lower_bound, b_ss_night_lower_bound, c_ss_night_lower_bound), 全体でベテランが少なくとも1名(s_night_lower_bound)必要
shift_constraints = pd.read_csv("data/pism/53-2-231-table-11.csv")
shift_constraints["day"] = shift_constraints["day"] - 1
display(shift_constraints)
day | day_of_week | holiday | all_day_lower_bound | all_day_upper_bound | all_night_lower_bound | all_night_upper_bound | a_day_lower_bound | a_day_upper_bound | a_night_lower_bound | a_night_upper_bound | b_day_lower_bound | b_day_upper_bound | b_night_lower_bound | b_night_upper_bound | c_day_lower_bound | c_day_upper_bound | c_night_lower_bound | c_night_upper_bound | s_day_lower_bound | s_day_upper_bound | s_night_lower_bound | s_night_upper_bound | a_ss_day_lower_bound | a_ss_day_upper_bound | a_ss_night_lower_bound | a_ss_night_upper_bound | b_ss_day_lower_bound | b_ss_day_upper_bound | b_ss_night_lower_bound | b_ss_night_upper_bound | c_ss_day_lower_bound | c_ss_day_upper_bound | c_ss_night_lower_bound | c_ss_night_upper_bound | rq_day_lower_bound | rq_day_upper_bound | rq_night_lower_bound | rq_night_upper_bound | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | Fri | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
1 | 1 | Sat | 0 | 9 | 10 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
2 | 2 | Sun | 0 | 9 | 9 | 4 | 4 | 3 | 3 | 1 | 2 | 3 | 3 | 1 | 2 | 3 | 3 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 0 | 7 | 0 | 1 |
3 | 3 | Mon | 1 | 9 | 9 | 4 | 4 | 3 | 3 | 1 | 2 | 3 | 3 | 1 | 2 | 3 | 3 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 0 | 7 | 0 | 1 |
4 | 4 | Tue | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
5 | 5 | Wed | 0 | 12 | 14 | 4 | 4 | 4 | 5 | 1 | 2 | 4 | 5 | 1 | 2 | 4 | 5 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
6 | 6 | Thu | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
7 | 7 | Fri | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
8 | 8 | Sat | 0 | 9 | 10 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
9 | 9 | Sun | 0 | 9 | 9 | 4 | 4 | 3 | 3 | 1 | 2 | 3 | 3 | 1 | 2 | 3 | 3 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 0 | 7 | 0 | 1 |
10 | 10 | Mon | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
11 | 11 | Tue | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
12 | 12 | Wed | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
13 | 13 | Thu | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
14 | 14 | Fri | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
15 | 15 | Sat | 0 | 9 | 10 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
16 | 16 | Sun | 0 | 9 | 9 | 4 | 4 | 3 | 3 | 1 | 2 | 3 | 3 | 1 | 2 | 3 | 3 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 0 | 7 | 0 | 1 |
17 | 17 | Mon | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
18 | 18 | Tue | 0 | 12 | 14 | 4 | 4 | 4 | 5 | 1 | 2 | 4 | 5 | 1 | 2 | 4 | 5 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
19 | 19 | Wed | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
20 | 20 | Thu | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
21 | 21 | Fri | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
22 | 22 | Sat | 1 | 9 | 10 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
23 | 23 | Sun | 0 | 9 | 9 | 4 | 4 | 3 | 3 | 1 | 2 | 3 | 3 | 1 | 2 | 3 | 3 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 2 | 3 | 1 | 2 | 0 | 7 | 0 | 1 |
24 | 24 | Mon | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
25 | 25 | Tue | 0 | 12 | 16 | 4 | 4 | 4 | 6 | 1 | 2 | 4 | 6 | 1 | 2 | 4 | 6 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 5 | 1 | 2 | 2 | 5 | 1 | 2 | 2 | 5 | 1 | 2 | 0 | 7 | 0 | 1 |
26 | 26 | Wed | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
27 | 27 | Thu | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
28 | 28 | Fri | 0 | 10 | 11 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
29 | 29 | Sat | 0 | 9 | 10 | 4 | 4 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 | 1 | 2 | 1 | 3 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 2 | 4 | 1 | 2 | 0 | 7 | 0 | 1 |
ナース拘束条件
ナースの休日, 日勤, 夜勤の回数の下限値と上限値(Type I拘束条件)
ナースの属性を読み込む。
項目 | 意味 |
---|---|
team | 所属チーム |
nurse_no | ナース |
skill_level | スキルレベル |
off_lower_bound | 休日の回数の下限 |
off_upper_bound | 休日の回数の上限 |
day_lower_bound | 日勤の回数の下限 |
day_upper_bound | 日勤の回数の上限 |
night_lower_bound | 夜勤の回数の下限 |
night_upper_bound | 夜勤の回数の上限 |
nurse_constraints = pd.read_csv("data/pism/53-2-231-table-12.csv")
nurse_constraints["nurse_no"] = nurse_constraints["nurse_no"] - 1
display(nurse_constraints)
team | nurse_no | skill_level | off_lower_bound | off_upper_bound | day_lower_bound | day_upper_bound | night_lower_bound | night_upper_bound | |
---|---|---|---|---|---|---|---|---|---|
0 | A | 0 | skilled | 9 | 10 | 9 | 12 | 4 | 5 |
1 | A | 1 | skilled | 9 | 10 | 10 | 13 | 4 | 5 |
2 | A | 2 | skilled | 9 | 10 | 10 | 13 | 4 | 5 |
3 | A | 3 | second_year | 9 | 10 | 10 | 13 | 4 | 5 |
4 | A | 4 | second_year | 9 | 10 | 10 | 13 | 4 | 5 |
5 | A | 5 | second_year | 9 | 10 | 10 | 13 | 4 | 5 |
6 | A | 6 | second_year | 9 | 10 | 9 | 12 | 4 | 5 |
7 | A | 7 | recently_qualified | 9 | 10 | 8 | 11 | 4 | 5 |
8 | A | 8 | recently_qualified | 9 | 10 | 9 | 12 | 4 | 5 |
9 | A | 9 | recently_qualified | 9 | 10 | 10 | 13 | 4 | 5 |
10 | B | 10 | skilled | 9 | 10 | 9 | 12 | 4 | 5 |
11 | B | 11 | skilled | 9 | 10 | 10 | 13 | 4 | 5 |
12 | B | 12 | second_year | 9 | 10 | 10 | 13 | 4 | 5 |
13 | B | 13 | second_year | 9 | 10 | 10 | 13 | 4 | 5 |
14 | B | 14 | second_year | 9 | 10 | 9 | 12 | 4 | 5 |
15 | B | 15 | second_year | 9 | 10 | 10 | 13 | 4 | 5 |
16 | B | 16 | second_year | 9 | 10 | 9 | 12 | 4 | 5 |
17 | B | 17 | recently_qualified | 9 | 10 | 9 | 12 | 4 | 5 |
18 | B | 18 | recently_qualified | 8 | 10 | 18 | 20 | 0 | 0 |
19 | C | 19 | skilled | 9 | 10 | 10 | 13 | 4 | 5 |
20 | C | 20 | skilled | 9 | 10 | 10 | 13 | 4 | 5 |
21 | C | 21 | second_year | 9 | 10 | 10 | 13 | 4 | 5 |
22 | C | 22 | second_year | 9 | 10 | 9 | 12 | 4 | 5 |
23 | C | 23 | second_year | 9 | 10 | 9 | 12 | 4 | 5 |
24 | C | 24 | second_year | 9 | 10 | 10 | 13 | 4 | 5 |
25 | C | 25 | second_year | 9 | 10 | 9 | 12 | 4 | 5 |
26 | C | 26 | recently_qualified | 9 | 10 | 8 | 11 | 4 | 5 |
27 | C | 27 | recently_qualified | 9 | 10 | 14 | 17 | 2 | 3 |
上限回数を合計しても勤務表作成対象の日数に満たないように見えるが、夜勤の後は夜勤明けというシフトになるので合計は30以上の値になっている。
nurse_constraints[
[
"off_upper_bound",
"day_upper_bound",
"night_upper_bound",
"night_upper_bound",
]
].sum(axis=1)
0 32
1 33
2 33
3 33
4 33
5 33
6 32
7 31
8 32
9 33
10 32
11 33
12 33
13 33
14 32
15 33
16 32
17 32
18 30
19 33
20 33
21 33
22 32
23 32
24 33
25 32
26 31
27 33
dtype: int64
前月末からのスケジュール(Type II拘束条件)
前月末の勤務表を読み込む。
記号 | 意味 |
---|---|
- | 日勤 |
N | 夜勤 |
n | 夜勤明け |
/ | 休日 |
+ | その他の勤務 |
拘束条件に4日連続の日勤は許されていない、とあるのだが2人が該当している。
last_shift_table_temp = pd.read_csv("data/pism/53-2-231-table-13-1.csv")
last_shift_table_temp["nurse_no"] = last_shift_table_temp["nurse_no"] - 1
display(last_shift_table_temp)
nurse_no | 26 | 27 | 28 | 29 | 30 | 31 | |
---|---|---|---|---|---|---|---|
0 | 0 | / | / | / | - | - | N |
1 | 1 | / | / | - | - | - | / |
2 | 2 | - | N | n | / | - | - |
3 | 3 | N | n | / | / | N | n |
4 | 4 | - | - | N | n | / | - |
5 | 5 | n | / | - | - | N | n |
6 | 6 | - | - | + | N | n | / |
7 | 7 | / | / | N | n | / | - |
8 | 8 | N | n | / | - | - | + |
9 | 9 | / | - | - | - | + | + |
10 | 10 | / | / | / | - | N | n |
11 | 11 | N | n | / | / | / | - |
12 | 12 | / | / | N | n | / | / |
13 | 13 | - | - | - | N | n | / |
14 | 14 | n | / | - | - | - | N |
15 | 15 | - | N | n | / | - | - |
16 | 16 | / | / | - | N | n | / |
17 | 17 | - | - | - | - | + | - |
18 | 18 | - | - | - | / | - | - |
19 | 19 | - | N | n | / | / | - |
20 | 20 | - | - | - | / | - | / |
21 | 21 | / | / | N | n | / | - |
22 | 22 | N | n | / | - | - | - |
23 | 23 | n | / | + | N | n | / |
24 | 24 | / | - | - | + | - | N |
25 | 25 | / | / | / | / | N | n |
26 | 26 | / | - | - | - | - | N |
27 | 27 | - | N | n | / | + | - |
後で使いやすいように変形する
last_shift_table = (
pd.DataFrame(
last_shift_table_temp.drop("nurse_no", axis=1).unstack(level=1)
)
.reset_index()
.dropna()
)
last_shift_table.columns = ["day", "staff", "shift"]
last_shift_table["day"] = last_shift_table["day"].astype(int) - 32
last_shift_table = pd.get_dummies(
last_shift_table,
columns=["shift"],
dtype=int,
prefix="",
prefix_sep="",
)
last_shift_table = (
last_shift_table.set_index(["staff", "day"]).stack().reset_index()
)
last_shift_table.columns = ["staff", "day", "shift", "var"]
display(last_shift_table)
staff | day | shift | var | |
---|---|---|---|---|
0 | 0 | -6 | + | 0 |
1 | 0 | -6 | - | 0 |
2 | 0 | -6 | / | 1 |
3 | 0 | -6 | N | 0 |
4 | 0 | -6 | n | 0 |
... | ... | ... | ... | ... |
835 | 27 | -1 | + | 0 |
836 | 27 | -1 | - | 1 |
837 | 27 | -1 | / | 0 |
838 | 27 | -1 | N | 0 |
839 | 27 | -1 | n | 0 |
840 rows × 4 columns
ナース達の勤務や休みの希望(Type II拘束条件)
記号 | 意味 |
---|---|
- | 日勤 |
N | 夜勤 |
n | 夜勤明け |
/ | 休日 |
+ | その他の勤務 |
* | 夜勤不可 |
x | 日勤不可 |
desired_shifts_temp = pd.read_csv("data/pism/53-2-231-table-13-2.csv")
desired_shifts_temp["nurse_no"] = desired_shifts_temp["nurse_no"] - 1
display(desired_shifts_temp)
nurse_no | 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 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | n | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | / | NaN | NaN | NaN |
1 | 1 | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | * | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | / | / | NaN | * |
2 | 2 | NaN | NaN | / | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3 | 3 | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
4 | 4 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
5 | 5 | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
6 | 6 | NaN | NaN | NaN | NaN | NaN | NaN | + | NaN | NaN | / | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
7 | 7 | NaN | NaN | NaN | NaN | NaN | + | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | / | / | x | NaN | NaN | NaN | NaN | NaN |
8 | 8 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | N | n | / | / | NaN | NaN | NaN | NaN | NaN |
9 | 9 | NaN | NaN | NaN | NaN | NaN | NaN | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
10 | 10 | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | / | / | NaN | NaN | NaN |
11 | 11 | NaN | NaN | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
12 | 12 | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | N | n | / | NaN | NaN | NaN | NaN |
13 | 13 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | / | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
14 | 14 | n | / | NaN | NaN | NaN | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | N | n | / |
15 | 15 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | N | n | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | N | n | / | NaN | NaN | NaN | NaN | NaN | NaN |
16 | 16 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
17 | 17 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
18 | 18 | NaN | NaN | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | + | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
19 | 19 | NaN | NaN | NaN | NaN | NaN | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
20 | 20 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
21 | 21 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
22 | 22 | NaN | NaN | NaN | NaN | NaN | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | / | x | NaN | NaN | NaN | NaN | NaN | NaN |
23 | 23 | NaN | NaN | NaN | NaN | NaN | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
24 | 24 | n | / | NaN | NaN | NaN | N | n | / | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | N | n | / | NaN | NaN | NaN | NaN |
25 | 25 | / | NaN | NaN | NaN | N | n | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | - |
26 | 26 | n | / | NaN | NaN | NaN | NaN | NaN | NaN | / | NaN | NaN | + | NaN | / | NaN | NaN | NaN | NaN | NaN | NaN | + | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
27 | 27 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
夜勤明けの翌日は休日である必要があるのだが、夜勤, 夜勤明け, その他の勤務という勤務を希望しているナースがいる(#25)。
夜勤不可は日勤・夜勤明け・その他の勤務・休日の何れか、日勤不可は夜勤・夜勤明け・その他の勤務・休日の何れかを希望していると解釈した。
その他の勤務はセミナー参加等ということで制約があるようにも思える。
後で使いやすいように変形する
desired_shifts = (
pd.DataFrame(desired_shifts_temp.drop("nurse_no", axis=1).unstack(level=1))
.reset_index()
.dropna()
)
desired_shifts.columns = ["day", "staff", "shift"]
desired_shifts["day"] = desired_shifts["day"].astype(int) - 1
desired_shifts = desired_shifts.reindex(
columns=["staff", "day", "shift"]
).sort_values(["staff", "day", "shift"])
display(desired_shifts)
staff | day | shift | |
---|---|---|---|
0 | 0 | 0 | n |
28 | 0 | 1 | / |
308 | 0 | 11 | + |
728 | 0 | 26 | / |
1 | 1 | 0 | / |
421 | 1 | 15 | * |
729 | 1 | 26 | / |
757 | 1 | 27 | / |
813 | 1 | 29 | * |
58 | 2 | 2 | / |
86 | 2 | 3 | / |
450 | 2 | 16 | / |
3 | 3 | 0 | / |
535 | 3 | 19 | / |
5 | 5 | 0 | / |
174 | 6 | 6 | + |
258 | 6 | 9 | / |
286 | 6 | 10 | / |
147 | 7 | 5 | + |
203 | 7 | 7 | + |
623 | 7 | 22 | / |
651 | 7 | 23 | / |
679 | 7 | 24 | x |
316 | 8 | 11 | + |
596 | 8 | 21 | N |
624 | 8 | 22 | n |
652 | 8 | 23 | / |
680 | 8 | 24 | / |
177 | 9 | 6 | / |
10 | 10 | 0 | / |
318 | 10 | 11 | + |
710 | 10 | 25 | / |
738 | 10 | 26 | / |
67 | 11 | 2 | / |
12 | 12 | 0 | / |
656 | 12 | 23 | N |
684 | 12 | 24 | n |
712 | 12 | 25 | / |
377 | 13 | 13 | / |
405 | 13 | 14 | / |
14 | 14 | 0 | n |
42 | 14 | 1 | / |
182 | 14 | 6 | + |
546 | 14 | 19 | / |
770 | 14 | 27 | N |
798 | 14 | 28 | n |
826 | 14 | 29 | / |
295 | 15 | 10 | N |
323 | 15 | 11 | n |
351 | 15 | 12 | / |
603 | 15 | 21 | N |
631 | 15 | 22 | n |
659 | 15 | 23 | / |
324 | 16 | 11 | + |
381 | 17 | 13 | + |
74 | 18 | 2 | / |
326 | 18 | 11 | + |
382 | 18 | 13 | + |
159 | 19 | 5 | / |
553 | 21 | 19 | / |
190 | 22 | 6 | + |
638 | 22 | 22 | / |
666 | 22 | 23 | x |
191 | 23 | 6 | + |
24 | 24 | 0 | n |
52 | 24 | 1 | / |
164 | 24 | 5 | N |
192 | 24 | 6 | n |
220 | 24 | 7 | / |
668 | 24 | 23 | N |
696 | 24 | 24 | n |
724 | 24 | 25 | / |
25 | 25 | 0 | / |
137 | 25 | 4 | N |
165 | 25 | 5 | n |
193 | 25 | 6 | + |
837 | 25 | 29 | - |
26 | 26 | 0 | n |
54 | 26 | 1 | / |
250 | 26 | 8 | / |
334 | 26 | 11 | + |
390 | 26 | 13 | / |
586 | 26 | 20 | + |
その他の制約(Type III)
- 各ナース, 少なくとも1回は週末2連休(2-3日, 3-4日, 9-10日, 16-17日, 23-24日の中から選ぶ. 4日は祭日)を必要とする
- 夜勤の翌日は休みでなければならない
- 夜勤と夜勤の間は少なくとも3日あけなければならない
- 1週間に1回は休みが入らなくてはならない
- 4日連続の日勤は許されていない
- 日勤, 休み, 日勤, 休み, 日勤というパターンは許されていない
- 前月末とのシフトの並びについても考慮しなければならない
モデルの作成
以上の制約条件からモデルを作成する。
目的関数はシフトの実現度とした。
from mip import Model, OptimizationStatus, maximize, xsum
model = Model(name="nurse_scheduling", solver_name="cbc")
model.threads = -1
staffs = nurse_constraints["nurse_no"]
staffs.name = "staff"
days = shift_constraints["day"]
days.name = "day"
shifts = pd.Series(["-", "N", "n", "/", "+"], name="shift")
num_staffs = staffs.shape[0]
num_days = days.shape[0]
num_shifts = shifts.shape[0]
# 当月のみの制約条件を作成する為に用いる
shift_table = pd.merge(pd.merge(staffs, days, "cross"), shifts, "cross")
shift_table["var"] = model.add_var_tensor(
(len(shift_table),), "var", var_type="B"
)
shift_table = shift_table.join(
nurse_constraints.set_index("nurse_no"),
on="staff",
how="left",
)
# 前月・当月を通して制約条件を作成する為に用いる
shift_temp = pd.concat(
[
last_shift_table.join(
nurse_constraints.set_index("nurse_no"),
on="staff",
how="left",
),
shift_table,
],
ignore_index=True,
)
shift_table = shift_table.join(
shift_constraints.set_index("day"),
on="day",
how="left",
)
"""
制約条件
"""
# 全員にシフトが割り当てられている
for _, shift in shift_table.groupby(["staff", "day"]):
model += xsum(shift["var"]) == 1
# 禁止パターン
forbidden_patterns = [
# 夜勤の翌日は夜勤明けでなければならない
["N", "-"],
["N", "/"],
["N", "+"],
# 夜勤開けの翌日は休みでなければならない
["n", "-"],
["n", "n"],
["n", "N"],
# 全ての拘束条件を満たしているとされる勤務表の例(表1)を見ると
# ナース#26の5日からのシフトでは夜勤明け, その他の勤務が許容されている。
# 勤務の希望(表13)を見るとこれは本人の希望のようである。
# ["n","+"],
#
# 夜勤明け, その他の勤務, 休日は許容されているようである
# しかし夜勤明け, その他の勤務, 日勤を禁止するとinfeasibleとなる
# ["n","+", "-"],
["n", "+", "N"],
["n", "+", "n"],
["n", "+", "+"],
["-", "n"],
["/", "n"],
["+", "n"],
# 日勤, 休み, 日勤, 休み, 日勤というパターンは許されていない
["-", "/", "-", "/", "-"],
# 4日連続の日勤は許されていない
["-", "-", "-", "-"],
]
shift_temp_days = shift_temp["day"].unique()
num_shift_temp_days = shift_temp_days.shape[0]
# 前月・当月通しての制約
for _, groupby_staff in shift_temp.groupby("staff"):
# 1週間に1回は休みが入らなくてはならない
window = 7
for days_in_window in [
shift_temp_days[i : i + window]
for i in range(0, num_shift_temp_days - window + 1, 1)
]:
model += (
xsum(
groupby_staff[
(groupby_staff["day"].isin(days_in_window))
& (groupby_staff["shift"] == "/")
]["var"]
)
>= 1
)
# 禁止パターン
for forbidden_pattern in forbidden_patterns:
window = len(forbidden_pattern)
for days_in_window in [
shift_temp_days[i : i + window]
for i in range(0, num_shift_temp_days - window + 1, 1)
]:
model += (
xsum(
[
groupby_staff.at[j, "var"]
for j in [
groupby_staff.index[
(groupby_staff["day"] == days_in_window[k])
& (
groupby_staff["shift"]
== forbidden_pattern[k]
)
].to_list()[0]
for k in range(len(days_in_window))
]
]
)
<= window - 1
)
# 夜勤と夜勤の間は少なくとも3日あけなければならない
# 夜勤明けを含めて5日間で計算する
window = 5
for days_in_window in [
shift_temp_days[i : i + window]
for i in range(0, num_shift_temp_days - window + 1, 1)
]:
model += (
xsum(
groupby_staff[
(groupby_staff["day"].isin(days_in_window))
& (groupby_staff["shift"] == "N")
]["var"]
)
<= 1
)
# 当月のみ、ナース毎の制約
for _, groupby_staff in shift_table.groupby("staff"):
# 日勤の上限・下限
for shift_name, shift_code in zip(
["day", "night", "off"], ["-", "N", "/"]
):
lower_bound = groupby_staff.at[
groupby_staff.index[0], "{}_lower_bound".format(shift_name)
]
upper_bound = groupby_staff.at[
groupby_staff.index[0], "{}_upper_bound".format(shift_name)
]
staff_sum = xsum(
groupby_staff[groupby_staff["shift"] == shift_code]["var"]
)
model += staff_sum >= lower_bound
model += staff_sum <= upper_bound
team_shift_skill_levels = [
[
"all_day",
["A", "B", "C"],
["skilled", "second_year", "recently_qualified"],
"-",
],
[
"all_night",
["A", "B", "C"],
["skilled", "second_year", "recently_qualified"],
"N",
],
["s_day", ["A", "B", "C"], ["skilled"], "-"],
["s_night", ["A", "B", "C"], ["skilled"], "N"],
["rq_day", ["A", "B", "C"], ["recently_qualified"], "-"],
["rq_night", ["A", "B", "C"], ["recently_qualified"], "N"],
["a_ss_day", ["A"], ["skilled", "second_year"], "-"],
["a_ss_night", ["A"], ["skilled", "second_year"], "N"],
["b_ss_day", ["B"], ["skilled", "second_year"], "-"],
["b_ss_night", ["B"], ["skilled", "second_year"], "N"],
["c_ss_day", ["C"], ["skilled", "second_year"], "-"],
["c_ss_night", ["C"], ["skilled", "second_year"], "N"],
["a_day", ["A"], ["skilled", "second_year", "recently_qualified"], "-"],
["a_night", ["A"], ["skilled", "second_year", "recently_qualified"], "N"],
["b_day", ["B"], ["skilled", "second_year", "recently_qualified"], "-"],
["b_night", ["B"], ["skilled", "second_year", "recently_qualified"], "N"],
["c_day", ["C"], ["skilled", "second_year", "recently_qualified"], "-"],
["c_night", ["C"], ["skilled", "second_year", "recently_qualified"], "N"],
]
# 日毎
for day, groupby_day in shift_table.groupby("day"):
for shift_name, team, skill_level, shift_code in team_shift_skill_levels:
lower_bound = groupby_day.at[
groupby_day.index[0], "{}_lower_bound".format(shift_name)
]
upper_bound = groupby_day.at[
groupby_day.index[0], "{}_upper_bound".format(shift_name)
]
num_assigned_staffs = xsum(
groupby_day[
(groupby_day["team"].isin(team))
& (groupby_day["skill_level"].isin(skill_level))
& (groupby_day["shift"] == shift_code)
]["var"]
)
model += num_assigned_staffs >= lower_bound
model += num_assigned_staffs <= upper_bound
# 各ナース, 少なくとも1回は週末2連休
# (2-3日, 3-4日, 9-10日, 16-17日, 23-24日の中から選ぶ.4日は祭日)
# を必要とする
holiday_in_rows = list(
map(
lambda x: list(map(lambda y: y - 1, x)),
[
[2, 3],
[3, 4],
[9, 10],
[16, 17],
[23, 24],
],
)
)
holiday_constr = model.add_var_tensor(
(
num_staffs,
len(holiday_in_rows),
),
"holiday_constr",
var_type="B",
)
for staff, groupby_staff in shift_table.groupby("staff"):
for i, holiday_in_a_row in enumerate(holiday_in_rows):
model += (
xsum(
[
groupby_staff.at[j, "var"]
for j in [
groupby_staff.index[
(groupby_staff["day"] == day)
& (groupby_staff["shift"] == "/")
].to_list()[0]
for day in holiday_in_a_row
]
]
)
== 2 * holiday_constr[staff, i]
)
model += xsum(holiday_constr[staff]) >= 1
"""
目的関数
"""
# シフトの希望が最大限叶うようにする
shift_satisfaction = desired_shifts.join(
shift_table.set_index(["day", "staff", "shift"]),
on=["day", "staff", "shift"],
how="left",
)
# 夜勤以外を希望 => 日勤・夜勤明け・休日
for index, row in shift_satisfaction[
shift_satisfaction["shift"].isin(["*"])
].iterrows():
shift_satisfaction = pd.concat(
[
shift_satisfaction,
shift_table[
(shift_table["day"] == row["day"])
& (shift_table["staff"] == row["staff"])
& (shift_table["shift"].isin(["-", "n", "+", "/"]))
],
],
ignore_index=True,
)
# 日勤以外を希望 => 夜勤・夜勤明け・休日
for index, row in shift_satisfaction[
shift_satisfaction["shift"].isin(["x"])
].iterrows():
shift_satisfaction = pd.concat(
[
shift_satisfaction,
shift_table[
(shift_table["day"] == row["day"])
& (shift_table["staff"] == row["staff"])
& (shift_table["shift"].isin(["N", "n", "+", "/"]))
],
],
ignore_index=True,
)
shift_satisfaction = shift_satisfaction[
shift_satisfaction["shift"].isin(["-", "N", "n", "/", "+"])
]
model.objective = maximize(xsum(shift_satisfaction["var"]))
model.optimize()
Welcome to the CBC MILP Solver
Version: devel
Build Date: Aug 4 2024
Starting solution of the Linear programming relaxation problem using Primal Simplex
Coin0506I Presolve 14089 (-1607) rows, 4059 (-281) columns and 54492 (-4728) elements
Clp0030I 37 infeas 0.081453527, obj 83.004957 - mu 0.0018794197, its 52, 3829 interior
Clp1000I sum of infeasibilities 5.24049e-05 - average 3.71956e-09, 152 fixed columns
Coin0506I Presolve 13092 (-997) rows, 3885 (-174) columns and 51396 (-3096) elements
Clp0006I 0 Obj 82.982614 Primal inf 8.0589436e-06 (18) Dual inf 8.1e+13 (150)
Clp0029I End of values pass after 3885 iterations
Clp0014I Perturbing problem by 0.001% of 1.5248741 - largest nonzero change 2.9978863e-05 ( 0.0014989431%) - largest zero change 2.963241e-05
Clp0000I Optimal - objective value 83
Clp0000I Optimal - objective value 83
Coin0511I After Postsolve, objective 83, infeasibilities - dual 0 (0), primal 0 (0)
Clp0006I 0 Obj 83 Dual inf 3.999995 (5)
Clp0014I Perturbing problem by 0.001% of 1.5148767 - largest nonzero change 2.9943914e-05 ( 0.0014971957%) - largest zero change 2.9976734e-05
Clp0000I Optimal - objective value 83
Clp0000I Optimal - objective value 83
Clp0000I Optimal - objective value 83
Coin0511I After Postsolve, objective 83, infeasibilities - dual 0 (0), primal 0 (0)
Clp0032I Optimal objective 83 - 0 iterations time 1.782, Presolve 0.03, Idiot 1.74
Starting MIP optimization
threads was changed from 0 to 20
maxSavedSolutions was changed from 1 to 10
Continuous objective value is 83 - 0.004914 seconds
Cgl0002I 112 variables fixed
Cgl0003I 77 fixed, 0 tightened bounds, 4664 strengthened rows, 2462 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 10457 strengthened rows, 91 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 6674 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 4948 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 2176 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 762 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 305 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 111 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 55 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 25 strengthened rows, 0 substitutions
Cgl0004I processed model has 7152 rows, 3092 columns (3092 integer (3092 of which binary)) and 47765 elements
Coin3009W Conflict graph built in 0.007 seconds, density: 0.076%
Cgl0015I Clique Strengthening extended 2 cliques, 6 were dominated
After applying Clique Strengthening continuous objective value is 78
Cbc0045I Nauty did not find any useful orbits in time 0.229306
Cbc0038I Initial state - 878 integers unsatisfied sum - 208.403
Cbc0038I Pass 1: (20.55 seconds) suminf. 87.71429 (420) obj. 73.4286 iterations 4990
Cbc0038I Pass 2: (23.52 seconds) suminf. 40.50199 (243) obj. 70.74 iterations 5871
Cbc0038I Pass 3: (27.06 seconds) suminf. 22.28571 (70) obj. 69.8571 iterations 6072
Cbc0038I Pass 4: (28.01 seconds) suminf. 0.00000 (0) obj. 69 iterations 1480
Cbc0038I Solution found of 69
Cbc0038I Before mini branch and bound, 1979 integers at bound fixed and 41 continuous
Cbc0038I Full problem 7148 rows 3092 columns, reduced to 2691 rows 756 columns
Cbc0038I Mini branch and bound improved solution from 69 to 69 (36.11 seconds)
Cbc0038I Round again with cutoff of 70.7999
Cbc0038I Pass 5: (36.13 seconds) suminf. 87.71429 (420) obj. 73.4286 iterations 0
Cbc0038I Pass 6: (38.22 seconds) suminf. 40.92920 (236) obj. 70.7999 iterations 4238
Cbc0038I Pass 7: (40.75 seconds) suminf. 25.13309 (96) obj. 70.7999 iterations 4271
Cbc0038I Pass 8: (41.09 seconds) suminf. 25.09490 (94) obj. 70.7999 iterations 488
Cbc0038I Pass 9: (41.36 seconds) suminf. 24.96113 (93) obj. 70.7999 iterations 375
Cbc0038I Pass 10: (42.18 seconds) suminf. 17.87483 (60) obj. 70.7999 iterations 1212
Cbc0038I Pass 11: (42.98 seconds) suminf. 11.79946 (64) obj. 70.7999 iterations 1195
Cbc0038I Pass 12: (44.24 seconds) suminf. 7.19919 (37) obj. 70.7999 iterations 2000
Cbc0038I Pass 13: (44.71 seconds) suminf. 1.20054 (6) obj. 70.7999 iterations 673
Cbc0038I Solution found of 71
Cbc0038I Before mini branch and bound, 1961 integers at bound fixed and 39 continuous
Cbc0038I Full problem 7148 rows 3092 columns, reduced to 2742 rows 777 columns
Cbc0038I Mini branch and bound did not improve solution (52.99 seconds)
Cbc0038I Round again with cutoff of 73.1999
Cbc0038I Pass 14: (53.45 seconds) suminf. 87.71429 (420) obj. 73.4286 iterations 0
Cbc0038I Pass 15: (56.11 seconds) suminf. 52.10027 (347) obj. 73.1999 iterations 5388
Cbc0038I Pass 16: (58.27 seconds) suminf. 31.42105 (127) obj. 73.1999 iterations 3852
Cbc0038I Pass 17: (61.23 seconds) suminf. 15.38822 (63) obj. 73.1999 iterations 4817
Cbc0038I Pass 18: (61.94 seconds) suminf. 12.03285 (61) obj. 73.1999 iterations 1080
Cbc0038I Pass 19: (62.34 seconds) suminf. 11.37460 (42) obj. 73.1999 iterations 580
Cbc0038I Pass 20: (62.62 seconds) suminf. 11.37460 (42) obj. 73.1999 iterations 399
Cbc0038I Pass 21: (62.70 seconds) suminf. 11.37460 (42) obj. 73.1999 iterations 101
Cbc0038I Pass 22: (62.80 seconds) suminf. 11.37460 (42) obj. 73.1999 iterations 120
Cbc0038I Pass 23: (62.89 seconds) suminf. 11.37460 (42) obj. 73.1999 iterations 106
Cbc0038I Pass 24: (63.85 seconds) suminf. 13.86619 (40) obj. 73.1999 iterations 1388
Cbc0038I Pass 25: (64.77 seconds) suminf. 10.39968 (41) obj. 73.1999 iterations 1380
Cbc0038I Pass 26: (65.09 seconds) suminf. 10.79968 (24) obj. 73.1999 iterations 436
Cbc0038I Pass 27: (65.38 seconds) suminf. 3.79968 (10) obj. 73.1999 iterations 375
Cbc0038I Pass 28: (65.57 seconds) suminf. 3.79968 (10) obj. 73.1999 iterations 255
Cbc0038I Pass 29: (65.75 seconds) suminf. 3.79968 (10) obj. 73.1999 iterations 213
Cbc0038I Pass 30: (66.37 seconds) suminf. 3.00000 (6) obj. 74 iterations 839
Cbc0038I Pass 31: (66.67 seconds) suminf. 3.00000 (6) obj. 74 iterations 420
Cbc0038I Pass 32: (66.73 seconds) suminf. 3.00000 (6) obj. 74 iterations 76
Cbc0038I Pass 33: (67.62 seconds) suminf. 7.19952 (18) obj. 73.1999 iterations 1362
Cbc0038I Pass 34: (68.71 seconds) suminf. 5.79968 (14) obj. 73.1999 iterations 1574
Cbc0038I Pass 35: (68.79 seconds) suminf. 3.79968 (10) obj. 73.1999 iterations 93
Cbc0038I Pass 36: (68.99 seconds) suminf. 3.79968 (10) obj. 73.1999 iterations 263
Cbc0038I Pass 37: (69.57 seconds) suminf. 3.00000 (6) obj. 74 iterations 782
Cbc0038I Pass 38: (69.81 seconds) suminf. 3.00000 (6) obj. 74 iterations 332
Cbc0038I Pass 39: (70.66 seconds) suminf. 3.39984 (8) obj. 73.1999 iterations 1256
Cbc0038I Pass 40: (71.37 seconds) suminf. 3.39984 (8) obj. 73.1999 iterations 1036
Cbc0038I Pass 41: (71.51 seconds) suminf. 3.39984 (8) obj. 73.1999 iterations 180
Cbc0038I Pass 42: (71.55 seconds) suminf. 3.39984 (8) obj. 73.1999 iterations 39
Cbc0038I Pass 43: (71.63 seconds) suminf. 3.39984 (8) obj. 73.1999 iterations 103
Cbc0038I No solution found this major pass
Cbc0038I Before mini branch and bound, 1903 integers at bound fixed and 39 continuous
Cbc0038I Full problem 7148 rows 3092 columns, reduced to 2846 rows 825 columns
Cbc0038I Mini branch and bound improved solution from 71 to 76 (79.77 seconds)
Cbc0038I Round again with cutoff of 77.2999
Cbc0038I Reduced cost fixing fixed 186 variables on major pass 4
Cbc0038I Pass 43: (81.30 seconds) suminf. 117.60168 (548) obj. 77.7801 iterations 2207
Cbc0038I Pass 44: (83.00 seconds) suminf. 92.56253 (454) obj. 77.7094 iterations 3497
Cbc0038I Pass 45: (84.53 seconds) suminf. 79.08765 (523) obj. 77.6998 iterations 3059
Cbc0038I Pass 46: (85.18 seconds) suminf. 74.92516 (599) obj. 77.6775 iterations 1392
Cbc0038I Pass 47: (85.74 seconds) suminf. 72.99388 (556) obj. 77.6234 iterations 1125
Cbc0038I Pass 48: (86.09 seconds) suminf. 72.25176 (700) obj. 77.6925 iterations 652
Cbc0038I Pass 49: (86.33 seconds) suminf. 70.51602 (563) obj. 77.6691 iterations 456
Cbc0038I Pass 50: (86.63 seconds) suminf. 69.12010 (485) obj. 77.6848 iterations 548
Cbc0038I Pass 51: (87.19 seconds) suminf. 69.04219 (642) obj. 77.6602 iterations 1055
Cbc0038I Pass 52: (87.69 seconds) suminf. 67.33903 (504) obj. 77.7208 iterations 900
Cbc0038I Pass 53: (88.06 seconds) suminf. 67.03349 (627) obj. 77.7362 iterations 651
Cbc0038I Pass 54: (88.34 seconds) suminf. 66.67160 (591) obj. 77.7352 iterations 521
Cbc0038I Pass 55: (88.51 seconds) suminf. 66.51749 (620) obj. 77.7299 iterations 281
Cbc0038I Pass 56: (89.03 seconds) suminf. 61.39625 (593) obj. 77.5215 iterations 1044
Cbc0038I Pass 57: (89.84 seconds) suminf. 57.48672 (722) obj. 77.5069 iterations 1639
Cbc0038I Pass 58: (90.06 seconds) suminf. 54.43044 (741) obj. 77.4195 iterations 445
Cbc0038I Pass 59: (90.46 seconds) suminf. 52.38335 (619) obj. 77.3283 iterations 846
Cbc0038I Pass 60: (90.79 seconds) suminf. 52.83048 (602) obj. 77.3333 iterations 641
Cbc0038I Pass 61: (90.99 seconds) suminf. 52.73466 (600) obj. 77.3212 iterations 303
Cbc0038I Pass 62: (91.36 seconds) suminf. 53.36578 (663) obj. 77.3716 iterations 716
Cbc0038I Pass 63: (91.71 seconds) suminf. 50.82215 (717) obj. 77.3302 iterations 712
Cbc0038I Pass 64: (92.30 seconds) suminf. 48.67360 (702) obj. 77.2999 iterations 1138
Cbc0038I Pass 65: (92.51 seconds) suminf. 48.53307 (645) obj. 77.2999 iterations 336
Cbc0038I Pass 66: (92.69 seconds) suminf. 48.15155 (670) obj. 77.2999 iterations 275
Cbc0038I Pass 67: (92.91 seconds) suminf. 47.91803 (684) obj. 77.2999 iterations 362
Cbc0038I Pass 68: (93.09 seconds) suminf. 48.66993 (710) obj. 77.3333 iterations 321
Cbc0038I Pass 69: (93.74 seconds) suminf. 55.67194 (586) obj. 77.3333 iterations 1511
Cbc0038I Pass 70: (94.67 seconds) suminf. 48.17802 (713) obj. 77.3106 iterations 2011
Cbc0038I Pass 71: (94.97 seconds) suminf. 47.93933 (691) obj. 77.2999 iterations 536
Cbc0038I Pass 72: (95.35 seconds) suminf. 48.52271 (774) obj. 77.3333 iterations 712
Cbc0038I No solution found this major pass
Cbc0038I Before mini branch and bound, 1381 integers at bound fixed and 31 continuous
Cbc0038I Mini branch and bound did not improve solution (95.37 seconds)
Cbc0038I After 95.37 seconds - Feasibility pump exiting with objective of 76 - took 77.04 seconds
Cbc0012I Integer solution of 76 found by feasibility pump after 0 iterations and 0 nodes (95.38 seconds)
Cbc0038I Full problem 7148 rows 3092 columns, reduced to 2176 rows 593 columns
Cbc0031I 6 added rows had average density of 27.5
Cbc0013I At root node, 6 cuts changed objective from 78 to 78 in 3 passes
Cbc0014I Cut generator 0 (Probing) - 1 row cuts average 8.0 elements, 15 column cuts (15 active) in 0.080 seconds - new frequency is 1
Cbc0014I Cut generator 1 (Gomory) - 5 row cuts average 115.6 elements, 0 column cuts (0 active) in 0.255 seconds - new frequency is 1
Cbc0014I Cut generator 2 (Knapsack) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.081 seconds - new frequency is -100
Cbc0014I Cut generator 3 (Clique) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.040 seconds - new frequency is -100
Cbc0014I Cut generator 4 (OddWheel) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.216 seconds - new frequency is -100
Cbc0014I Cut generator 5 (MixedIntegerRounding2) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.021 seconds - new frequency is -100
Cbc0014I Cut generator 6 (FlowCover) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.001 seconds - new frequency is -100
Cbc0014I Cut generator 7 (TwoMirCuts) - 46 row cuts average 103.1 elements, 0 column cuts (0 active) in 0.333 seconds - new frequency is -100
Cbc0014I Cut generator 8 (ZeroHalf) - 4 row cuts average 7.2 elements, 0 column cuts (0 active) in 0.784 seconds - new frequency is -100
Cbc0010I After 0 nodes, 1 on tree, 76 best solution, best possible 78 (104.45 seconds)
Cbc0010I After 1 nodes, 2 on tree, 76 best solution, best possible 78 (107.23 seconds)
Cbc0010I After 2 nodes, 2 on tree, 76 best solution, best possible 78 (109.74 seconds)
Cbc0010I After 4 nodes, 2 on tree, 76 best solution, best possible 78 (111.28 seconds)
Cbc0010I After 5 nodes, 1 on tree, 76 best solution, best possible 78 (112.94 seconds)
Cbc0010I After 7 nodes, 2 on tree, 76 best solution, best possible 78 (113.78 seconds)
Cbc0010I After 9 nodes, 2 on tree, 76 best solution, best possible 78 (114.60 seconds)
Cbc0010I After 11 nodes, 2 on tree, 76 best solution, best possible 78 (116.24 seconds)
Cbc0010I After 13 nodes, 2 on tree, 76 best solution, best possible 78 (117.05 seconds)
Cbc0010I After 17 nodes, 1 on tree, 76 best solution, best possible 78 (118.65 seconds)
Cbc0010I After 19 nodes, 2 on tree, 76 best solution, best possible 78 (119.68 seconds)
Cbc0010I After 24 nodes, 1 on tree, 76 best solution, best possible 78 (121.04 seconds)
Cbc0010I After 26 nodes, 2 on tree, 76 best solution, best possible 78 (122.25 seconds)
Cbc0010I After 28 nodes, 1 on tree, 76 best solution, best possible 78 (123.10 seconds)
Cbc0010I After 34 nodes, 6 on tree, 76 best solution, best possible 78 (123.99 seconds)
Cbc0010I After 37 nodes, 7 on tree, 76 best solution, best possible 78 (124.69 seconds)
Cbc0010I After 40 nodes, 7 on tree, 76 best solution, best possible 78 (125.87 seconds)
Cbc0010I After 42 nodes, 8 on tree, 76 best solution, best possible 78 (126.60 seconds)
Cbc0010I After 48 nodes, 12 on tree, 76 best solution, best possible 78 (127.51 seconds)
Cbc0010I After 53 nodes, 16 on tree, 76 best solution, best possible 78 (128.22 seconds)
Cbc0010I After 55 nodes, 16 on tree, 76 best solution, best possible 78 (129.23 seconds)
Cbc0010I After 60 nodes, 18 on tree, 76 best solution, best possible 78 (130.31 seconds)
Cbc0010I After 64 nodes, 21 on tree, 76 best solution, best possible 78 (131.35 seconds)
Cbc0010I After 68 nodes, 25 on tree, 76 best solution, best possible 78 (132.24 seconds)
Cbc0010I After 76 nodes, 29 on tree, 76 best solution, best possible 78 (133.13 seconds)
Cbc0010I After 78 nodes, 31 on tree, 76 best solution, best possible 78 (133.90 seconds)
Cbc0010I After 82 nodes, 34 on tree, 76 best solution, best possible 78 (134.72 seconds)
Cbc0010I After 86 nodes, 38 on tree, 76 best solution, best possible 78 (135.51 seconds)
Cbc0010I After 89 nodes, 40 on tree, 76 best solution, best possible 78 (136.29 seconds)
Cbc0010I After 93 nodes, 43 on tree, 76 best solution, best possible 78 (137.02 seconds)
Cbc0010I After 99 nodes, 47 on tree, 76 best solution, best possible 78 (138.09 seconds)
Cbc0010I After 102 nodes, 49 on tree, 76 best solution, best possible 78 (138.88 seconds)
Cbc0010I After 106 nodes, 50 on tree, 76 best solution, best possible 78 (139.92 seconds)
Cbc0010I After 109 nodes, 51 on tree, 76 best solution, best possible 78 (140.67 seconds)
Cbc0010I After 114 nodes, 54 on tree, 76 best solution, best possible 78 (141.81 seconds)
Cbc0010I After 118 nodes, 57 on tree, 76 best solution, best possible 78 (142.75 seconds)
Cbc0010I After 123 nodes, 60 on tree, 76 best solution, best possible 78 (143.79 seconds)
Cbc0010I After 127 nodes, 63 on tree, 76 best solution, best possible 78 (145.27 seconds)
Cbc0010I After 132 nodes, 67 on tree, 76 best solution, best possible 78 (146.66 seconds)
Cbc0010I After 137 nodes, 69 on tree, 76 best solution, best possible 78 (147.40 seconds)
Cbc0010I After 141 nodes, 71 on tree, 76 best solution, best possible 78 (148.25 seconds)
Cbc0010I After 144 nodes, 73 on tree, 76 best solution, best possible 78 (149.21 seconds)
Cbc0010I After 146 nodes, 74 on tree, 76 best solution, best possible 78 (150.79 seconds)
Cbc0010I After 149 nodes, 76 on tree, 76 best solution, best possible 78 (151.65 seconds)
Cbc0010I After 156 nodes, 81 on tree, 76 best solution, best possible 78 (152.61 seconds)
Cbc0010I After 159 nodes, 83 on tree, 76 best solution, best possible 78 (154.08 seconds)
Cbc0010I After 164 nodes, 85 on tree, 76 best solution, best possible 78 (155.40 seconds)
Cbc0010I After 169 nodes, 90 on tree, 76 best solution, best possible 78 (156.37 seconds)
Cbc0010I After 172 nodes, 92 on tree, 76 best solution, best possible 78 (157.33 seconds)
Cbc0010I After 176 nodes, 93 on tree, 76 best solution, best possible 78 (158.41 seconds)
Cbc0010I After 179 nodes, 95 on tree, 76 best solution, best possible 78 (159.15 seconds)
Cbc0010I After 182 nodes, 97 on tree, 76 best solution, best possible 78 (160.06 seconds)
Cbc0010I After 187 nodes, 98 on tree, 76 best solution, best possible 78 (161.14 seconds)
Cbc0010I After 191 nodes, 100 on tree, 76 best solution, best possible 78 (162.18 seconds)
Cbc0010I After 197 nodes, 105 on tree, 76 best solution, best possible 78 (163.02 seconds)
Cbc0010I After 201 nodes, 108 on tree, 76 best solution, best possible 78 (164.01 seconds)
Cbc0010I After 203 nodes, 108 on tree, 76 best solution, best possible 78 (164.84 seconds)
Cbc0010I After 206 nodes, 110 on tree, 76 best solution, best possible 78 (165.92 seconds)
Cbc0010I After 209 nodes, 112 on tree, 76 best solution, best possible 78 (166.77 seconds)
Cbc0010I After 213 nodes, 113 on tree, 76 best solution, best possible 78 (167.91 seconds)
Cbc0010I After 219 nodes, 117 on tree, 76 best solution, best possible 78 (168.62 seconds)
Cbc0010I After 224 nodes, 120 on tree, 76 best solution, best possible 78 (169.69 seconds)
Cbc0010I After 226 nodes, 122 on tree, 76 best solution, best possible 78 (170.58 seconds)
Cbc0010I After 228 nodes, 123 on tree, 76 best solution, best possible 78 (171.42 seconds)
Cbc0010I After 229 nodes, 124 on tree, 76 best solution, best possible 78 (172.13 seconds)
Cbc0010I After 232 nodes, 124 on tree, 76 best solution, best possible 78 (173.21 seconds)
Cbc0010I After 239 nodes, 127 on tree, 76 best solution, best possible 78 (173.94 seconds)
Cbc0010I After 245 nodes, 131 on tree, 76 best solution, best possible 78 (174.91 seconds)
Cbc0010I After 247 nodes, 132 on tree, 76 best solution, best possible 78 (175.91 seconds)
Cbc0010I After 248 nodes, 132 on tree, 76 best solution, best possible 78 (176.99 seconds)
Cbc0010I After 252 nodes, 135 on tree, 76 best solution, best possible 78 (178.00 seconds)
Cbc0010I After 260 nodes, 142 on tree, 76 best solution, best possible 78 (179.14 seconds)
Cbc0010I After 266 nodes, 145 on tree, 76 best solution, best possible 78 (180.27 seconds)
Cbc0010I After 268 nodes, 146 on tree, 76 best solution, best possible 78 (181.20 seconds)
Cbc0010I After 270 nodes, 147 on tree, 76 best solution, best possible 78 (182.16 seconds)
Cbc0010I After 273 nodes, 149 on tree, 76 best solution, best possible 78 (182.93 seconds)
Cbc0010I After 279 nodes, 154 on tree, 76 best solution, best possible 78 (183.75 seconds)
Cbc0010I After 284 nodes, 157 on tree, 76 best solution, best possible 78 (184.72 seconds)
Cbc0010I After 287 nodes, 157 on tree, 76 best solution, best possible 78 (185.72 seconds)
Cbc0010I After 289 nodes, 159 on tree, 76 best solution, best possible 78 (186.46 seconds)
Cbc0010I After 292 nodes, 161 on tree, 76 best solution, best possible 78 (187.20 seconds)
Cbc0010I After 297 nodes, 163 on tree, 76 best solution, best possible 78 (188.05 seconds)
Cbc0010I After 301 nodes, 165 on tree, 76 best solution, best possible 78 (189.32 seconds)
Cbc0010I After 305 nodes, 166 on tree, 76 best solution, best possible 78 (190.09 seconds)
Cbc0010I After 308 nodes, 169 on tree, 76 best solution, best possible 78 (190.95 seconds)
Cbc0010I After 310 nodes, 169 on tree, 76 best solution, best possible 78 (191.77 seconds)
Cbc0010I After 317 nodes, 174 on tree, 76 best solution, best possible 78 (192.59 seconds)
Cbc0010I After 320 nodes, 176 on tree, 76 best solution, best possible 78 (193.60 seconds)
Cbc0010I After 322 nodes, 178 on tree, 76 best solution, best possible 78 (194.48 seconds)
Cbc0010I After 328 nodes, 183 on tree, 76 best solution, best possible 78 (195.20 seconds)
Cbc0010I After 334 nodes, 186 on tree, 76 best solution, best possible 78 (196.10 seconds)
Cbc0010I After 336 nodes, 186 on tree, 76 best solution, best possible 78 (196.85 seconds)
Cbc0010I After 340 nodes, 188 on tree, 76 best solution, best possible 78 (197.65 seconds)
Cbc0010I After 345 nodes, 192 on tree, 76 best solution, best possible 78 (198.42 seconds)
Cbc0010I After 349 nodes, 194 on tree, 76 best solution, best possible 78 (199.20 seconds)
Cbc0010I After 354 nodes, 197 on tree, 76 best solution, best possible 78 (200.29 seconds)
Cbc0010I After 359 nodes, 202 on tree, 76 best solution, best possible 78 (201.02 seconds)
Cbc0010I After 364 nodes, 203 on tree, 76 best solution, best possible 78 (201.88 seconds)
Cbc0010I After 367 nodes, 206 on tree, 76 best solution, best possible 78 (202.62 seconds)
Cbc0010I After 371 nodes, 209 on tree, 76 best solution, best possible 78 (203.34 seconds)
Cbc0010I After 377 nodes, 210 on tree, 76 best solution, best possible 78 (204.09 seconds)
Cbc0010I After 382 nodes, 213 on tree, 76 best solution, best possible 78 (205.04 seconds)
Cbc0010I After 387 nodes, 216 on tree, 76 best solution, best possible 78 (206.01 seconds)
Cbc0010I After 392 nodes, 219 on tree, 76 best solution, best possible 78 (206.72 seconds)
Cbc0010I After 395 nodes, 220 on tree, 76 best solution, best possible 78 (207.43 seconds)
Cbc0010I After 402 nodes, 225 on tree, 76 best solution, best possible 78 (208.18 seconds)
Cbc0010I After 408 nodes, 227 on tree, 76 best solution, best possible 78 (209.42 seconds)
Cbc0010I After 416 nodes, 232 on tree, 76 best solution, best possible 78 (210.33 seconds)
Cbc0010I After 419 nodes, 234 on tree, 76 best solution, best possible 78 (211.31 seconds)
Cbc0010I After 429 nodes, 239 on tree, 76 best solution, best possible 78 (212.12 seconds)
Cbc0010I After 433 nodes, 242 on tree, 76 best solution, best possible 78 (212.95 seconds)
Cbc0010I After 436 nodes, 243 on tree, 76 best solution, best possible 78 (213.71 seconds)
Cbc0010I After 440 nodes, 245 on tree, 76 best solution, best possible 78 (214.46 seconds)
Cbc0010I After 448 nodes, 250 on tree, 76 best solution, best possible 78 (215.29 seconds)
Cbc0010I After 455 nodes, 256 on tree, 76 best solution, best possible 78 (216.66 seconds)
Cbc0010I After 460 nodes, 257 on tree, 76 best solution, best possible 78 (217.43 seconds)
Cbc0010I After 468 nodes, 260 on tree, 76 best solution, best possible 78 (218.44 seconds)
Cbc0010I After 473 nodes, 263 on tree, 76 best solution, best possible 78 (219.22 seconds)
Cbc0010I After 481 nodes, 267 on tree, 76 best solution, best possible 78 (220.05 seconds)
Cbc0010I After 487 nodes, 273 on tree, 76 best solution, best possible 78 (220.83 seconds)
Cbc0010I After 491 nodes, 274 on tree, 76 best solution, best possible 78 (221.69 seconds)
Cbc0010I After 500 nodes, 279 on tree, 76 best solution, best possible 78 (222.45 seconds)
Cbc0010I After 503 nodes, 279 on tree, 76 best solution, best possible 78 (223.28 seconds)
Cbc0010I After 512 nodes, 284 on tree, 76 best solution, best possible 78 (224.28 seconds)
Cbc0010I After 519 nodes, 288 on tree, 76 best solution, best possible 78 (225.73 seconds)
Cbc0010I After 526 nodes, 292 on tree, 76 best solution, best possible 78 (226.60 seconds)
Cbc0010I After 536 nodes, 297 on tree, 76 best solution, best possible 78 (227.31 seconds)
Cbc0010I After 541 nodes, 300 on tree, 76 best solution, best possible 78 (228.10 seconds)
Cbc0010I After 544 nodes, 302 on tree, 76 best solution, best possible 78 (228.98 seconds)
Cbc0010I After 552 nodes, 306 on tree, 76 best solution, best possible 78 (229.69 seconds)
Cbc0010I After 559 nodes, 311 on tree, 76 best solution, best possible 78 (230.41 seconds)
Cbc0010I After 563 nodes, 313 on tree, 76 best solution, best possible 78 (231.11 seconds)
Cbc0010I After 567 nodes, 314 on tree, 76 best solution, best possible 78 (231.98 seconds)
Cbc0010I After 574 nodes, 316 on tree, 76 best solution, best possible 78 (232.81 seconds)
Cbc0010I After 583 nodes, 321 on tree, 76 best solution, best possible 78 (233.72 seconds)
Cbc0010I After 590 nodes, 325 on tree, 76 best solution, best possible 78 (234.44 seconds)
Cbc0010I After 592 nodes, 326 on tree, 76 best solution, best possible 78 (235.28 seconds)
Cbc0010I After 598 nodes, 328 on tree, 76 best solution, best possible 78 (236.00 seconds)
Cbc0010I After 609 nodes, 336 on tree, 76 best solution, best possible 78 (236.80 seconds)
Cbc0010I After 613 nodes, 339 on tree, 76 best solution, best possible 78 (237.53 seconds)
Cbc0010I After 620 nodes, 341 on tree, 76 best solution, best possible 78 (238.36 seconds)
Cbc0010I After 628 nodes, 344 on tree, 76 best solution, best possible 78 (239.30 seconds)
Cbc0010I After 634 nodes, 350 on tree, 76 best solution, best possible 78 (240.14 seconds)
Cbc0010I After 639 nodes, 353 on tree, 76 best solution, best possible 78 (240.93 seconds)
Cbc0010I After 648 nodes, 359 on tree, 76 best solution, best possible 78 (241.71 seconds)
Cbc0010I After 651 nodes, 360 on tree, 76 best solution, best possible 78 (242.44 seconds)
Cbc0010I After 659 nodes, 364 on tree, 76 best solution, best possible 78 (243.31 seconds)
Cbc0010I After 666 nodes, 365 on tree, 76 best solution, best possible 78 (244.16 seconds)
Cbc0010I After 675 nodes, 373 on tree, 76 best solution, best possible 78 (245.11 seconds)
Cbc0010I After 681 nodes, 375 on tree, 76 best solution, best possible 78 (245.82 seconds)
Cbc0010I After 688 nodes, 379 on tree, 76 best solution, best possible 78 (246.79 seconds)
Cbc0010I After 694 nodes, 383 on tree, 76 best solution, best possible 78 (247.50 seconds)
Cbc0010I After 700 nodes, 386 on tree, 76 best solution, best possible 78 (248.21 seconds)
Cbc0010I After 706 nodes, 389 on tree, 76 best solution, best possible 78 (249.30 seconds)
Cbc0010I After 714 nodes, 393 on tree, 76 best solution, best possible 78 (250.05 seconds)
Cbc0010I After 724 nodes, 400 on tree, 76 best solution, best possible 78 (250.91 seconds)
Cbc0010I After 727 nodes, 402 on tree, 76 best solution, best possible 78 (251.62 seconds)
Cbc0010I After 734 nodes, 408 on tree, 76 best solution, best possible 78 (252.47 seconds)
Cbc0010I After 741 nodes, 412 on tree, 76 best solution, best possible 78 (253.31 seconds)
Cbc0010I After 745 nodes, 415 on tree, 76 best solution, best possible 78 (254.03 seconds)
Cbc0010I After 754 nodes, 419 on tree, 76 best solution, best possible 78 (255.30 seconds)
Cbc0010I After 762 nodes, 422 on tree, 76 best solution, best possible 78 (256.01 seconds)
Cbc0010I After 769 nodes, 425 on tree, 76 best solution, best possible 78 (256.80 seconds)
Cbc0010I After 774 nodes, 429 on tree, 76 best solution, best possible 78 (257.73 seconds)
Cbc0010I After 781 nodes, 435 on tree, 76 best solution, best possible 78 (258.51 seconds)
Cbc0010I After 787 nodes, 438 on tree, 76 best solution, best possible 78 (259.23 seconds)
Cbc0010I After 795 nodes, 442 on tree, 76 best solution, best possible 78 (260.22 seconds)
Cbc0010I After 804 nodes, 446 on tree, 76 best solution, best possible 78 (260.93 seconds)
Cbc0010I After 809 nodes, 449 on tree, 76 best solution, best possible 78 (261.63 seconds)
Cbc0010I After 815 nodes, 452 on tree, 76 best solution, best possible 78 (262.39 seconds)
Cbc0010I After 819 nodes, 453 on tree, 76 best solution, best possible 78 (263.15 seconds)
Cbc0010I After 827 nodes, 458 on tree, 76 best solution, best possible 78 (264.15 seconds)
Cbc0010I After 835 nodes, 464 on tree, 76 best solution, best possible 78 (265.04 seconds)
Cbc0010I After 841 nodes, 467 on tree, 76 best solution, best possible 78 (265.76 seconds)
Cbc0010I After 845 nodes, 469 on tree, 76 best solution, best possible 78 (266.52 seconds)
Cbc0010I After 851 nodes, 472 on tree, 76 best solution, best possible 78 (267.30 seconds)
Cbc0010I After 858 nodes, 477 on tree, 76 best solution, best possible 78 (268.00 seconds)
Cbc0010I After 863 nodes, 481 on tree, 76 best solution, best possible 78 (268.81 seconds)
Cbc0010I After 869 nodes, 483 on tree, 76 best solution, best possible 78 (269.55 seconds)
Cbc0010I After 871 nodes, 484 on tree, 76 best solution, best possible 78 (270.28 seconds)
Cbc0010I After 877 nodes, 488 on tree, 76 best solution, best possible 78 (270.99 seconds)
Cbc0010I After 886 nodes, 493 on tree, 76 best solution, best possible 78 (272.02 seconds)
Cbc0010I After 891 nodes, 496 on tree, 76 best solution, best possible 78 (272.88 seconds)
Cbc0010I After 897 nodes, 500 on tree, 76 best solution, best possible 78 (273.79 seconds)
Cbc0010I After 904 nodes, 503 on tree, 76 best solution, best possible 78 (274.56 seconds)
Cbc0010I After 907 nodes, 503 on tree, 76 best solution, best possible 78 (275.37 seconds)
Cbc0010I After 913 nodes, 505 on tree, 76 best solution, best possible 78 (276.07 seconds)
Cbc0010I After 922 nodes, 510 on tree, 76 best solution, best possible 78 (276.82 seconds)
Cbc0010I After 926 nodes, 511 on tree, 76 best solution, best possible 78 (277.82 seconds)
Cbc0010I After 930 nodes, 513 on tree, 76 best solution, best possible 78 (278.53 seconds)
Cbc0010I After 937 nodes, 515 on tree, 76 best solution, best possible 78 (279.34 seconds)
Cbc0010I After 943 nodes, 519 on tree, 76 best solution, best possible 78 (280.21 seconds)
Cbc0004I Integer solution of 78 found after 711006 iterations and 937 nodes (280.60 seconds)
Cbc0012I Integer solution of 78 found by heuristic after 718472 iterations and 944 nodes (280.60 seconds)
Cbc0004I Integer solution of 78 found after 718822 iterations and 944 nodes (282.52 seconds)
Cbc0030I Thread 0 used 56 times, waiting to start 0.73805904, 285 locks, 0.021552563 locked, 0.0001168251 waiting for locks
Cbc0030I Thread 1 used 51 times, waiting to start 3.5307941, 262 locks, 0.01808691 locked, 7.8201294e-05 waiting for locks
Cbc0030I Thread 2 used 51 times, waiting to start 7.1101875, 262 locks, 0.019477129 locked, 0.00023293495 waiting for locks
Cbc0030I Thre
<OptimizationStatus.OPTIMAL: 0>
if model.status == OptimizationStatus.OPTIMAL:
model.write("data/pism/nurse-scheduling.lp")
shift_satisfaction["val"] = shift_satisfaction["var"].astype(float)
not_satisfied = shift_satisfaction[
shift_satisfaction["val"] < 1
].sort_values(["staff", "day", "shift"])[["staff", "day", "shift", "val"]]
# シフト希望 "*", "x"は候補の中で1つ希望が叶えばよい
dups = not_satisfied[
not_satisfied.duplicated(keep=False, subset=["day", "staff"])
].sort_values(["staff", "day", "shift"])[["staff", "day", "shift"]]
print("'x', '*' シフト")
display(dups)
non_dups = (
pd.merge(
not_satisfied,
dups,
on=["staff", "day", "shift"],
how="outer",
indicator=True,
)
.query('_merge != "both"')
.sort_values(["staff", "day", "shift"])[["staff", "day", "shift"]]
)
print("希望か叶わなかったシフト")
display(non_dups)
print(
"シフトの実現度: {}%".format(
int(
100
* model.objective_value
/ (shift_satisfaction.shape[0] - dups.shape[0])
)
)
)
shift_table["val"] = shift_table["var"].astype(float).round().astype(int)
answer = shift_table[shift_table["val"] > 0.5]
pivot = answer.pivot(
columns="day", index="staff", values="shift"
).reset_index()
def highlight_not_satisfied(row, target, props):
staff = row.name
style = [
(
props
if any(t[0] == staff and t[1] == day for t in target)
else ""
)
for day, shift in row.items()
]
return style
styled = pivot.style.apply(
highlight_not_satisfied,
target=non_dups.to_numpy(),
props="background-color: #ffff00; color: #ff0000; font-weight: bold;",
axis=1,
)
def add_div(func):
def wrapper(*args, **kwargs):
return '<div class="dataframe">{}</div>'.format(
func(*args, **kwargs)
)
return wrapper
styled.to_html = add_div(styled.to_html)
display(styled)
else:
print(model.status)
'x', '*' シフト
staff | day | shift | |
---|---|---|---|
86 | 1 | 15 | + |
83 | 1 | 15 | - |
85 | 1 | 15 | / |
90 | 1 | 29 | + |
89 | 1 | 29 | / |
88 | 1 | 29 | n |
94 | 7 | 24 | + |
91 | 7 | 24 | N |
92 | 7 | 24 | n |
98 | 22 | 23 | + |
95 | 22 | 23 | N |
96 | 22 | 23 | n |
希望か叶わなかったシフト
staff | day | shift | |
---|---|---|---|
6 | 2 | 2 | / |
7 | 2 | 3 | / |
11 | 8 | 23 | / |
12 | 15 | 23 | / |
16 | 26 | 8 | / |
シフトの実現度: 93%
day | staff | 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 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | n | / | / | / | N | n | / | - | - | N | n | + | - | / | - | - | - | N | n | / | - | - | N | n | / | - | / | N | n | / |
1 | 1 | / | N | n | + | - | / | N | n | + | - | - | - | / | - | N | n | + | - | / | N | n | / | / | / | - | - | / | / | - | - |
2 | 2 | N | n | + | - | / | N | n | / | - | - | N | n | / | / | - | / | / | - | N | n | / | - | - | - | N | n | / | - | - | / |
3 | 3 | / | - | - | - | / | - | - | N | n | + | - | / | - | N | n | / | / | / | - | / | - | N | n | + | / | - | - | / | N | n |
4 | 4 | - | + | - | - | - | / | - | - | N | n | / | - | / | - | - | N | n | / | - | - | N | n | / | / | - | N | n | / | / | / |
5 | 5 | / | + | - | N | n | / | - | - | / | / | / | - | N | n | / | - | - | - | / | - | N | n | + | - | - | / | N | n | / | - |
6 | 6 | - | - | N | n | / | - | + | - | / | / | / | N | n | / | / | - | N | n | / | - | / | - | - | N | n | / | - | - | / | N |
7 | 7 | - | - | N | n | / | + | / | + | + | - | N | n | / | - | / | + | - | N | n | / | - | - | / | / | / | N | n | / | - | - |
8 | 8 | - | / | / | / | - | - | - | / | - | N | n | + | - | / | N | n | + | / | - | - | / | N | n | + | / | / | - | - | N | n |
9 | 9 | / | N | n | + | - | - | / | N | n | + | - | - | / | - | - | / | / | - | - | N | n | / | - | - | / | - | N | n | / | / |
10 | 10 | / | - | - | N | n | / | - | - | N | n | / | + | - | N | n | / | / | / | - | - | / | - | - | N | n | / | / | - | N | n |
11 | 11 | - | / | / | / | - | - | - | N | n | + | / | - | - | - | / | N | n | / | / | - | N | n | + | - | - | / | N | n | / | - |
12 | 12 | / | N | n | + | / | - | N | n | / | / | - | - | N | n | / | - | - | - | N | n | / | / | - | N | n | / | - | - | / | - |
13 | 13 | / | + | - | - | - | N | n | / | / | / | - | N | n | / | / | - | - | N | n | / | - | / | - | - | N | n | / | - | - | N |
14 | 14 | n | / | / | / | N | n | + | - | - | - | / | N | n | / | - | - | N | n | / | / | - | - | N | n | / | - | - | N | n | / |
15 | 15 | N | n | + | - | / | N | n | / | - | - | N | n | / | - | - | / | / | - | - | / | / | N | n | + | - | - | - | / | - | / |
16 | 16 | - | - | N | n | / | - | / | - | - | N | n | + | / | - | N | n | + | / | - | N | n | / | / | / | - | N | n | / | - | / |
17 | 17 | / | - | - | - | N | n | / | / | / | / | - | - | - | + | / | - | N | n | / | - | - | - | N | n | / | - | - | N | n | / |
18 | 18 | - | / | / | / | - | - | - | / | - | - | - | + | / | + | - | + | - | - | - | / | - | / | + | - | - | - | / | - | - | - |
19 | 19 | - | - | N | n | / | / | - | N | n | + | - | / | N | n | / | / | / | - | - | / | - | N | n | + | - | / | - | - | / | N |
20 | 20 | N | n | + | - | / | - | N | n | / | / | - | N | n | / | - | - | N | n | / | - | - | / | - | - | / | N | n | / | / | - |
21 | 21 | - | + | - | N | n | / | - | - | / | / | - | - | N | n | / | - | - | N | n | / | / | - | N | n | / | - | / | N | n | / |
22 | 22 | / | N | n | + | - | - | + | / | - | N | n | / | - | - | N | n | + | - | / | - | N | n | / | / | - | - | / | / | - | / |
23 | 23 | - | - | - | N | n | / | + | - | N | n | / | / | - | N | n | + | - | / | - | N | n | / | / | / | - | - | N | n | / | / |
24 | 24 | n | / | / | / | - | N | n | / | - | - | N | n | / | - | - | N | n | / | - | - | / | / | - | N | n | / | - | - | N | n |
25 | 25 | / | - | - | - | N | n | + | / | - | - | / | - | - | N | n | / | / | / | N | n | / | - | + | - | N | n | / | / | - | - |
26 | 26 | n | / | / | / | - | - | N | n | + | - | / | + | - | / | / | - | - | - | N | n | + | / | - | - | N | n | / | / | - | N |
27 | 27 | N | n | + | - | / | - | - | - | N | n | / | - | / | - | - | N | n | / | - | / | - | - | / | / | / | - | - | - | / | - |
夜勤のシフトをうまくスケジュールできていない。
(続く)
データの出典
- 池上 敦子. ナース・スケジューリング--調査・モデル化・アルゴリズム. 統計数理 = Proceedings of the Institute of Statistical Mathematics. 53(2) (通号 102) 2005,p.231~259.