├── Benchmarks ├── CustomInstance.txt ├── Instance1.txt ├── Instance10.txt ├── Instance11.txt ├── Instance12.txt ├── Instance13.txt ├── Instance14.txt ├── Instance15.txt ├── Instance16.txt ├── Instance17.txt ├── Instance18.txt ├── Instance19.txt ├── Instance2.txt ├── Instance20.txt ├── Instance21.txt ├── Instance22.txt ├── Instance23.txt ├── Instance24.txt ├── Instance3.txt ├── Instance4.txt ├── Instance5.txt ├── Instance6.txt ├── Instance7.txt ├── Instance8.txt └── Instance9.txt ├── README.md ├── acp24-sumschool-xcp.slides.pdf ├── ecai2024_practice_part1.ipynb ├── ecai2024_practice_part2.ipynb ├── ecai2024_practice_part3.ipynb ├── ecai2024_presentation.ipynb ├── ecai2024_presentation.pdf ├── explanations ├── __init__.py ├── counterfactual.py ├── diagnosis.py ├── marco_mcs_mus.py ├── stepwise │ ├── __init__.py │ ├── backward.py │ ├── datastructures.py │ ├── forward.py │ └── propagate.py └── subset.py ├── factory.py ├── hands-on-tutorial slides.pdf ├── hands-on-tutorial.slides.html ├── img ├── allcons.png ├── app_explanations.png ├── beluga.png ├── change_model.png ├── changing_solution.png ├── chatopt.png ├── chatopt1.png ├── chatopt2.png ├── chatopt3.png ├── chatopt4.png ├── coloring_mcs.png ├── coloring_mus.png ├── cpmpy-intro.png ├── cpmpy_transformations.png ├── erc.jpg ├── explain_step-wise.png ├── explain_unsat.png ├── fixing_mcs.png ├── fixing_relax.png ├── hittingset.png ├── interaction_figure.png ├── interaction_figure2.png ├── interaction_figure3.png ├── interaction_figure4.png ├── intro_ai.png ├── inverse_opt.png ├── kul.jpg ├── maxconsequence.png ├── mcs.png ├── model_reconciliation.png ├── model_solve.png ├── mss.png ├── mus.png ├── mus_assum.png ├── musses.png ├── nurse_rost_prob.jpg ├── ocus.png ├── onestep_example.png ├── onestep_expl.png ├── onestep_mus.png ├── order_delmus.png ├── plant_layout_expl.png ├── prob2sol.png ├── qr-code.png ├── quickxplain.png ├── slack.png ├── slide_cdcl1.png ├── slide_cdcl2.png ├── slide_cdcl3.png ├── slide_cdcl4.png ├── smus.png ├── smus_efficient.png ├── solutions_vizual.png ├── stepwise.png ├── stepwise_pseudo.png ├── task_alloc_expl.png ├── tuples_logo.jpeg ├── tutorial_thumbnail.png ├── why_not_better.png ├── why_not_better2.png ├── workforce_ex.png ├── workforce_restore_feas.png ├── workforce_restore_infeas.png ├── workforce_review.png ├── workforce_solve.png └── workforce_workflow.png ├── previous_tutorials ├── acp24-sumschool-xcp.ipynb └── hands-on-tutorial.ipynb ├── read_data.py ├── requirements.txt └── visualize.py /Benchmarks/CustomInstance.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 14 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | L,480,E 11 | 12 | SECTION_STAFF 13 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 14 | A,E=14|L=14,5280,3360,5,2,2,2 15 | B,E=14|L=14,5280,3360,7,2,2,1 16 | C,E=14|L=14,5280,3360,5,2,2,2 17 | D,E=14|L=0,5280,3360,7,2,2,1 18 | E,E=0|L=14,5280,3360,5,2,2,2 19 | F,E=14|L=14,5280,3360,5,2,2,2 20 | G,E=14|L=14,5280,3360,7,2,2,1 21 | H,E=14|L=14,5280,3360,5,2,2,2 22 | I,E=14|L=14,5280,3360,7,2,2,1 23 | 24 | SECTION_DAYS_OFF 25 | # EmployeeID, DayIndexes (start at zero) 26 | A,3 27 | B,1 28 | C,2 29 | D,12 30 | E,1 31 | F,13 32 | G,9 33 | H,3 34 | I,0 35 | 36 | SECTION_SHIFT_ON_REQUESTS 37 | # EmployeeID, Day, ShiftID, Weight 38 | A,8,L,1 39 | A,9,L,1 40 | B,7,E,1 41 | B,8,E,1 42 | B,9,E,1 43 | B,10,E,1 44 | C,8,E,1 45 | C,9,E,1 46 | C,10,E,1 47 | C,11,E,1 48 | D,1,E,1 49 | D,2,E,1 50 | D,3,E,1 51 | E,3,L,1 52 | E,4,L,1 53 | E,5,L,1 54 | E,6,L,1 55 | E,7,L,1 56 | E,12,L,2 57 | E,13,L,2 58 | F,3,L,3 59 | F,4,L,3 60 | H,2,L,2 61 | H,10,E,2 62 | H,7,L,2 63 | H,5,L,2 64 | 65 | 66 | SECTION_SHIFT_OFF_REQUESTS 67 | # EmployeeID, Day, ShiftID, Weight 68 | A,2,L,2 69 | A,2,E,2 70 | A,7,L,2 71 | A,5,L,2 72 | B,3,L,2 73 | B,5,L,2 74 | C,5,L,2 75 | D,5,L,2 76 | F,5,L,2 77 | F,7,L,2 78 | G,5,L,2 79 | G,3,E,2 80 | G,4,E,2 81 | G,5,E,2 82 | G,6,E,2 83 | G,7,E,2 84 | H,1,L,2 85 | H,6,E,2 86 | H,9,L,2 87 | H,12,L,2 88 | 89 | 90 | SECTION_COVER 91 | # Day, ShiftID, Requirement, Weight for under, Weight for over 92 | 0,E,4,100,1 93 | 0,L,3,100,1 94 | 1,E,4,100,1 95 | 1,L,3,100,1 96 | 2,E,3,100,1 97 | 2,L,5,100,1 98 | 3,E,5,100,1 99 | 3,L,3,100,1 100 | 4,E,3,100,1 101 | 4,L,3,100,1 102 | 5,E,5,100,1 103 | 5,L,3,100,1 104 | 6,E,5,100,1 105 | 6,L,3,100,1 106 | 7,E,3,100,1 107 | 7,L,2,100,1 108 | 8,E,4,100,1 109 | 8,L,3,100,1 110 | 9,E,4,100,1 111 | 9,L,3,100,1 112 | 10,E,4,100,1 113 | 10,L,3,100,1 114 | 11,E,2,100,1 115 | 11,L,3,100,1 116 | 12,E,4,100,1 117 | 12,L,3,100,1 118 | 13,E,3,100,1 119 | 13,L,5,100,1 120 | -------------------------------------------------------------------------------- /Benchmarks/Instance1.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 14 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | D,480, 10 | 11 | SECTION_STAFF 12 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 13 | A,D=14,4320,3360,5,2,2,1 14 | B,D=14,4320,3360,5,2,2,1 15 | C,D=14,4320,3360,5,2,2,1 16 | D,D=14,4320,3360,5,2,2,1 17 | E,D=14,4320,3360,5,2,2,1 18 | F,D=14,4320,3360,5,2,2,1 19 | G,D=14,4320,3360,5,2,2,1 20 | H,D=14,4320,3360,5,2,2,1 21 | 22 | SECTION_DAYS_OFF 23 | # EmployeeID, DayIndexes (start at zero) 24 | A,0 25 | B,5 26 | C,8 27 | D,2 28 | E,9 29 | F,5 30 | G,1 31 | H,7 32 | 33 | SECTION_SHIFT_ON_REQUESTS 34 | # EmployeeID, Day, ShiftID, Weight 35 | A,2,D,2 36 | A,3,D,2 37 | B,0,D,3 38 | B,1,D,3 39 | B,2,D,3 40 | B,3,D,3 41 | B,4,D,3 42 | C,0,D,1 43 | C,1,D,1 44 | C,2,D,1 45 | C,3,D,1 46 | C,4,D,1 47 | D,8,D,2 48 | D,9,D,2 49 | F,0,D,2 50 | F,1,D,2 51 | H,9,D,1 52 | H,10,D,1 53 | H,11,D,1 54 | H,12,D,1 55 | H,13,D,1 56 | 57 | SECTION_SHIFT_OFF_REQUESTS 58 | # EmployeeID, Day, ShiftID, Weight 59 | C,12,D,1 60 | C,13,D,1 61 | F,8,D,3 62 | H,2,D,3 63 | H,3,D,3 64 | 65 | SECTION_COVER 66 | # Day, ShiftID, Requirement, Weight for under, Weight for over 67 | 0,D,5,100,1 68 | 1,D,7,100,1 69 | 2,D,6,100,1 70 | 3,D,4,100,1 71 | 4,D,5,100,1 72 | 5,D,5,100,1 73 | 6,D,5,100,1 74 | 7,D,6,100,1 75 | 8,D,7,100,1 76 | 9,D,4,100,1 77 | 10,D,2,100,1 78 | 11,D,5,100,1 79 | 12,D,6,100,1 80 | 13,D,4,100,1 81 | -------------------------------------------------------------------------------- /Benchmarks/Instance10.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 28 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | d1,480,E 11 | d2,480,E 12 | L,480,E|d1|d2 13 | N,600,E|d1|d2|L 14 | 15 | SECTION_STAFF 16 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 17 | A,E=0|d1=28|d2=0|L=28|N=0,8640,7560,5,2,2,2 18 | B,E=28|d1=0|d2=9|L=28|N=0,8640,7560,5,2,2,2 19 | C,E=28|d1=28|d2=0|L=28|N=0,8640,7560,5,2,2,2 20 | D,E=0|d1=28|d2=9|L=28|N=5,8640,7560,5,2,2,2 21 | E,E=28|d1=0|d2=0|L=28|N=5,8640,7560,5,2,2,2 22 | F,E=0|d1=28|d2=0|L=28|N=5,8640,7560,5,2,2,2 23 | G,E=28|d1=0|d2=9|L=28|N=5,8640,7560,5,2,2,2 24 | H,E=0|d1=28|d2=9|L=0|N=0,8640,7560,5,2,2,2 25 | I,E=28|d1=28|d2=0|L=0|N=5,8640,7560,5,2,2,2 26 | J,E=0|d1=28|d2=0|L=28|N=5,8640,7560,5,2,2,2 27 | K,E=0|d1=28|d2=0|L=0|N=5,8640,7560,5,2,2,2 28 | L,E=28|d1=28|d2=9|L=28|N=5,8640,7560,5,2,2,2 29 | M,E=28|d1=28|d2=0|L=28|N=0,8640,7560,5,2,2,2 30 | N,E=0|d1=0|d2=0|L=28|N=0,8640,7560,5,2,2,2 31 | O,E=28|d1=0|d2=9|L=28|N=0,8640,7560,5,2,2,2 32 | P,E=28|d1=28|d2=9|L=28|N=5,8640,7560,5,2,2,2 33 | Q,E=28|d1=28|d2=9|L=28|N=0,8640,7560,5,2,2,2 34 | R,E=28|d1=28|d2=9|L=0|N=5,8640,7560,5,2,2,2 35 | S,E=28|d1=28|d2=9|L=28|N=5,8640,7560,5,2,2,2 36 | T,E=0|d1=0|d2=9|L=28|N=0,8640,7560,5,2,2,2 37 | U,E=28|d1=0|d2=9|L=28|N=0,8640,7560,5,2,2,2 38 | V,E=28|d1=28|d2=9|L=28|N=5,8640,7560,5,2,2,2 39 | W,E=28|d1=28|d2=9|L=0|N=5,8640,7560,5,2,2,2 40 | X,E=28|d1=28|d2=0|L=0|N=0,8640,7560,5,2,2,2 41 | Y,E=28|d1=28|d2=9|L=28|N=0,8640,7560,5,2,2,2 42 | Z,E=28|d1=28|d2=9|L=28|N=5,8640,7560,5,2,2,2 43 | AA,E=0|d1=28|d2=0|L=28|N=0,8640,7560,5,2,2,2 44 | AB,E=28|d1=28|d2=9|L=0|N=5,8640,7560,5,2,2,2 45 | AC,E=28|d1=28|d2=0|L=28|N=5,8640,7560,5,2,2,2 46 | AD,E=28|d1=0|d2=9|L=28|N=0,8640,7560,5,2,2,2 47 | AE,E=0|d1=28|d2=8|L=28|N=0,8160,7080,5,2,2,2 48 | AF,E=28|d1=0|d2=8|L=28|N=0,8160,7080,5,2,2,2 49 | AG,E=28|d1=0|d2=0|L=0|N=0,8160,7080,5,2,2,2 50 | AH,E=28|d1=0|d2=8|L=28|N=0,8160,7080,5,2,2,2 51 | AI,E=28|d1=28|d2=0|L=28|N=5,8160,7080,5,2,2,2 52 | AJ,E=28|d1=28|d2=8|L=28|N=5,8160,7080,5,2,2,2 53 | AK,E=28|d1=28|d2=8|L=28|N=0,8160,7080,5,2,2,2 54 | AL,E=0|d1=28|d2=8|L=28|N=5,8160,7080,5,2,2,2 55 | AM,E=28|d1=0|d2=0|L=0|N=5,8160,7080,5,2,2,2 56 | AN,E=28|d1=28|d2=8|L=28|N=5,8160,7080,5,2,2,2 57 | 58 | SECTION_DAYS_OFF 59 | # EmployeeID, DayIndexes (start at zero) 60 | A,3,7 61 | B,14,15 62 | C,23,24 63 | D,11,12 64 | E,12,13 65 | F,2,3 66 | G,1,11 67 | H,10,11 68 | I,7,22 69 | J,22,27 70 | K,3,21 71 | L,7,8 72 | M,3,4 73 | N,22,23 74 | O,5,8 75 | P,1,2 76 | Q,21,24 77 | R,4,5 78 | S,3,11 79 | T,8,9 80 | U,2,14 81 | V,12,19 82 | W,11,26 83 | X,23,27 84 | Y,16,22 85 | Z,1,12 86 | AA,20,21 87 | AB,25,26 88 | AC,13,27 89 | AD,0,23 90 | AE,14,15 91 | AF,6,7 92 | AG,5,8 93 | AH,8,9 94 | AI,26,27 95 | AJ,14,15 96 | AK,2,18 97 | AL,3,23 98 | AM,0,9 99 | AN,7,19 100 | 101 | SECTION_SHIFT_ON_REQUESTS 102 | # EmployeeID, Day, ShiftID, Weight 103 | A,10,L,3 104 | A,11,L,3 105 | A,19,d1,2 106 | A,20,d1,2 107 | A,21,d1,2 108 | A,22,d1,2 109 | A,23,d1,2 110 | B,12,E,3 111 | C,3,L,2 112 | C,4,L,2 113 | C,5,L,2 114 | C,6,L,2 115 | C,7,L,2 116 | C,12,E,1 117 | C,13,E,1 118 | C,14,E,1 119 | C,15,E,1 120 | D,3,N,2 121 | D,4,N,2 122 | D,5,N,2 123 | D,6,N,2 124 | D,13,d2,3 125 | D,18,L,2 126 | D,26,d1,1 127 | E,8,L,1 128 | E,9,L,1 129 | E,10,L,1 130 | E,11,L,1 131 | F,4,N,2 132 | F,5,N,2 133 | F,6,N,2 134 | F,12,d1,3 135 | F,13,d1,3 136 | F,14,d1,3 137 | F,15,d1,3 138 | F,16,d1,3 139 | G,3,d2,1 140 | G,4,d2,1 141 | G,5,d2,1 142 | H,3,d1,1 143 | H,13,d1,2 144 | J,2,N,1 145 | J,3,N,1 146 | J,4,N,1 147 | J,5,N,1 148 | J,6,N,1 149 | J,10,d1,2 150 | J,14,N,2 151 | J,15,N,2 152 | J,16,N,2 153 | J,25,N,1 154 | J,26,N,1 155 | K,16,N,3 156 | K,17,N,3 157 | K,18,N,3 158 | K,19,N,3 159 | K,20,N,3 160 | L,11,E,1 161 | L,12,E,1 162 | L,19,d2,3 163 | L,20,d2,3 164 | L,21,d2,3 165 | L,22,d2,3 166 | L,23,d2,3 167 | M,11,L,1 168 | M,12,L,1 169 | M,13,L,1 170 | M,14,L,1 171 | M,19,E,2 172 | M,20,E,2 173 | M,21,E,2 174 | M,22,E,2 175 | N,3,L,2 176 | N,4,L,2 177 | N,5,L,2 178 | N,15,L,3 179 | O,0,L,2 180 | O,1,L,2 181 | O,2,L,2 182 | O,3,L,2 183 | O,4,L,2 184 | O,22,d2,1 185 | O,23,d2,1 186 | P,3,L,1 187 | P,4,L,1 188 | P,5,L,1 189 | P,19,d1,1 190 | P,20,d1,1 191 | P,21,d1,1 192 | P,26,E,2 193 | P,27,E,2 194 | Q,2,E,1 195 | Q,3,E,1 196 | Q,4,E,1 197 | Q,5,E,1 198 | Q,13,L,2 199 | Q,14,L,2 200 | Q,15,L,2 201 | R,0,d1,2 202 | R,7,E,3 203 | R,8,E,3 204 | R,12,d1,2 205 | R,18,E,3 206 | R,19,E,3 207 | R,20,E,3 208 | R,26,d1,1 209 | S,5,L,1 210 | S,6,L,1 211 | S,7,L,1 212 | S,8,L,1 213 | S,13,d1,1 214 | S,14,d1,1 215 | S,15,d1,1 216 | S,16,d1,1 217 | T,6,d2,2 218 | T,13,L,1 219 | T,14,L,1 220 | U,8,L,3 221 | U,9,L,3 222 | U,10,L,3 223 | U,11,L,3 224 | U,12,L,3 225 | V,3,L,3 226 | V,4,L,3 227 | V,5,L,3 228 | V,6,L,3 229 | V,7,L,3 230 | W,3,N,3 231 | W,4,N,3 232 | W,5,N,3 233 | W,6,N,3 234 | X,2,d1,2 235 | X,6,E,2 236 | X,7,E,2 237 | X,8,E,2 238 | X,9,E,2 239 | X,10,E,2 240 | X,15,E,2 241 | X,16,E,2 242 | X,17,E,2 243 | X,18,E,2 244 | Z,14,E,2 245 | Z,15,E,2 246 | Z,16,E,2 247 | Z,17,E,2 248 | AA,1,d1,1 249 | AA,9,d1,3 250 | AA,10,d1,3 251 | AA,11,d1,3 252 | AA,12,d1,3 253 | AB,16,d2,1 254 | AB,17,d2,1 255 | AB,18,d2,1 256 | AB,19,d2,1 257 | AD,19,E,2 258 | AD,20,E,2 259 | AD,21,E,2 260 | AD,22,E,2 261 | AE,2,d1,1 262 | AE,16,L,1 263 | AE,17,L,1 264 | AE,18,L,1 265 | AE,23,d2,1 266 | AE,24,d2,1 267 | AE,25,d2,1 268 | AE,26,d2,1 269 | AE,27,d2,1 270 | AF,2,E,1 271 | AF,3,E,1 272 | AF,18,d2,2 273 | AF,19,d2,2 274 | AF,20,d2,2 275 | AF,21,d2,2 276 | AG,0,E,1 277 | AG,1,E,1 278 | AG,2,E,1 279 | AG,3,E,1 280 | AG,4,E,1 281 | AG,10,E,1 282 | AG,11,E,1 283 | AG,12,E,1 284 | AG,18,E,3 285 | AG,19,E,3 286 | AG,20,E,3 287 | AG,24,E,1 288 | AH,18,E,3 289 | AH,19,E,3 290 | AH,20,E,3 291 | AI,16,L,2 292 | AI,17,L,2 293 | AI,18,L,2 294 | AI,19,L,2 295 | AI,20,L,2 296 | AJ,21,N,3 297 | AL,10,d1,2 298 | AL,11,d1,2 299 | AL,12,d1,2 300 | AL,13,d1,2 301 | AM,2,E,3 302 | AM,3,E,3 303 | AM,15,E,3 304 | AM,16,E,3 305 | AM,17,E,3 306 | AN,11,L,3 307 | AN,12,L,3 308 | AN,13,L,3 309 | AN,21,E,2 310 | AN,22,E,2 311 | AN,23,E,2 312 | AN,24,E,2 313 | 314 | SECTION_SHIFT_OFF_REQUESTS 315 | # EmployeeID, Day, ShiftID, Weight 316 | B,1,d2,3 317 | B,2,d2,3 318 | B,3,d2,3 319 | B,4,d2,3 320 | B,5,d2,3 321 | E,1,L,2 322 | E,2,L,2 323 | E,16,L,1 324 | E,17,L,1 325 | E,18,L,1 326 | G,21,E,2 327 | G,22,E,2 328 | G,23,E,2 329 | G,24,E,2 330 | G,25,E,2 331 | I,25,E,1 332 | I,26,E,1 333 | L,3,d1,2 334 | L,4,d1,2 335 | L,5,d1,2 336 | L,6,d1,2 337 | O,10,L,3 338 | O,11,L,3 339 | O,12,L,3 340 | O,13,L,3 341 | O,14,L,3 342 | S,20,N,2 343 | S,21,N,2 344 | S,22,N,2 345 | S,23,N,2 346 | U,19,L,3 347 | U,20,L,3 348 | U,21,L,3 349 | U,22,L,3 350 | Y,9,d2,3 351 | Y,10,d2,3 352 | Y,11,d2,3 353 | Y,12,d2,3 354 | Y,13,d2,3 355 | Z,2,N,2 356 | Z,3,N,2 357 | Z,4,N,2 358 | Z,5,N,2 359 | Z,6,N,2 360 | Z,21,d1,3 361 | AB,3,d1,1 362 | AB,4,d1,1 363 | AB,5,d1,1 364 | AC,0,E,2 365 | AC,6,L,2 366 | AC,7,L,2 367 | AC,8,L,2 368 | AD,2,L,3 369 | AD,3,L,3 370 | AD,4,L,3 371 | AD,5,L,3 372 | AF,11,E,3 373 | AF,12,E,3 374 | AF,13,E,3 375 | AF,14,E,3 376 | AH,0,d2,1 377 | AH,1,d2,1 378 | AH,2,d2,1 379 | AH,10,E,3 380 | AH,11,E,3 381 | AI,10,E,1 382 | AK,10,d2,1 383 | AK,11,d2,1 384 | AK,12,d2,1 385 | AK,13,d2,1 386 | AL,5,N,2 387 | AL,6,N,2 388 | AL,17,N,1 389 | AL,18,N,1 390 | 391 | SECTION_COVER 392 | # Day, ShiftID, Requirement, Weight for under, Weight for over 393 | 0,E,7,100,1 394 | 0,d1,3,100,1 395 | 0,d2,6,100,1 396 | 0,L,7,100,1 397 | 0,N,1,100,1 398 | 1,E,5,100,1 399 | 1,d1,5,100,1 400 | 1,d2,6,100,1 401 | 1,L,6,100,1 402 | 1,N,2,100,1 403 | 2,E,6,100,1 404 | 2,d1,5,100,1 405 | 2,d2,5,100,1 406 | 2,L,6,100,1 407 | 2,N,2,100,1 408 | 3,E,6,100,1 409 | 3,d1,4,100,1 410 | 3,d2,3,100,1 411 | 3,L,7,100,1 412 | 3,N,2,100,1 413 | 4,E,6,100,1 414 | 4,d1,5,100,1 415 | 4,d2,4,100,1 416 | 4,L,6,100,1 417 | 4,N,1,100,1 418 | 5,E,7,100,1 419 | 5,d1,5,100,1 420 | 5,d2,4,100,1 421 | 5,L,6,100,1 422 | 5,N,3,100,1 423 | 6,E,6,100,1 424 | 6,d1,6,100,1 425 | 6,d2,7,100,1 426 | 6,L,5,100,1 427 | 6,N,3,100,1 428 | 7,E,6,100,1 429 | 7,d1,5,100,1 430 | 7,d2,5,100,1 431 | 7,L,6,100,1 432 | 7,N,2,100,1 433 | 8,E,8,100,1 434 | 8,d1,5,100,1 435 | 8,d2,5,100,1 436 | 8,L,5,100,1 437 | 8,N,4,100,1 438 | 9,E,6,100,1 439 | 9,d1,5,100,1 440 | 9,d2,3,100,1 441 | 9,L,5,100,1 442 | 9,N,1,100,1 443 | 10,E,5,100,1 444 | 10,d1,5,100,1 445 | 10,d2,5,100,1 446 | 10,L,6,100,1 447 | 10,N,1,100,1 448 | 11,E,7,100,1 449 | 11,d1,5,100,1 450 | 11,d2,5,100,1 451 | 11,L,6,100,1 452 | 11,N,1,100,1 453 | 12,E,5,100,1 454 | 12,d1,5,100,1 455 | 12,d2,5,100,1 456 | 12,L,7,100,1 457 | 12,N,3,100,1 458 | 13,E,7,100,1 459 | 13,d1,5,100,1 460 | 13,d2,7,100,1 461 | 13,L,8,100,1 462 | 13,N,1,100,1 463 | 14,E,8,100,1 464 | 14,d1,6,100,1 465 | 14,d2,5,100,1 466 | 14,L,7,100,1 467 | 14,N,2,100,1 468 | 15,E,7,100,1 469 | 15,d1,5,100,1 470 | 15,d2,5,100,1 471 | 15,L,6,100,1 472 | 15,N,1,100,1 473 | 16,E,9,100,1 474 | 16,d1,4,100,1 475 | 16,d2,5,100,1 476 | 16,L,7,100,1 477 | 16,N,3,100,1 478 | 17,E,6,100,1 479 | 17,d1,5,100,1 480 | 17,d2,5,100,1 481 | 17,L,6,100,1 482 | 17,N,1,100,1 483 | 18,E,7,100,1 484 | 18,d1,6,100,1 485 | 18,d2,5,100,1 486 | 18,L,7,100,1 487 | 18,N,2,100,1 488 | 19,E,6,100,1 489 | 19,d1,5,100,1 490 | 19,d2,5,100,1 491 | 19,L,5,100,1 492 | 19,N,3,100,1 493 | 20,E,7,100,1 494 | 20,d1,5,100,1 495 | 20,d2,6,100,1 496 | 20,L,7,100,1 497 | 20,N,2,100,1 498 | 21,E,6,100,1 499 | 21,d1,5,100,1 500 | 21,d2,6,100,1 501 | 21,L,7,100,1 502 | 21,N,1,100,1 503 | 22,E,6,100,1 504 | 22,d1,6,100,1 505 | 22,d2,6,100,1 506 | 22,L,6,100,1 507 | 22,N,3,100,1 508 | 23,E,4,100,1 509 | 23,d1,5,100,1 510 | 23,d2,7,100,1 511 | 23,L,6,100,1 512 | 23,N,2,100,1 513 | 24,E,8,100,1 514 | 24,d1,3,100,1 515 | 24,d2,3,100,1 516 | 24,L,7,100,1 517 | 24,N,1,100,1 518 | 25,E,7,100,1 519 | 25,d1,5,100,1 520 | 25,d2,5,100,1 521 | 25,L,7,100,1 522 | 25,N,2,100,1 523 | 26,E,7,100,1 524 | 26,d1,6,100,1 525 | 26,d2,4,100,1 526 | 26,L,7,100,1 527 | 26,N,2,100,1 528 | 27,E,7,100,1 529 | 27,d1,6,100,1 530 | 27,d2,4,100,1 531 | 27,L,6,100,1 532 | 27,N,1,100,1 533 | -------------------------------------------------------------------------------- /Benchmarks/Instance11.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 28 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | a1,480, 10 | a2,480, 11 | d1,480,a1|a2 12 | d2,480,a1|a2 13 | p1,480,a1|a2|d1|d2 14 | p2,480,a1|a2|d1|d2 15 | 16 | SECTION_STAFF 17 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 18 | A,a1=28|a2=0|d1=0|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 19 | B,a1=28|a2=28|d1=0|d2=0|p1=28|p2=28,8640,7560,5,2,2,2 20 | C,a1=28|a2=28|d1=0|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 21 | D,a1=28|a2=0|d1=28|d2=28|p1=28|p2=0,8640,7560,5,2,2,2 22 | E,a1=28|a2=28|d1=0|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 23 | F,a1=0|a2=28|d1=0|d2=0|p1=0|p2=28,8640,7560,5,2,2,2 24 | G,a1=28|a2=28|d1=0|d2=28|p1=28|p2=0,8640,7560,5,2,2,2 25 | H,a1=28|a2=28|d1=28|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 26 | I,a1=0|a2=28|d1=0|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 27 | J,a1=0|a2=28|d1=0|d2=0|p1=28|p2=28,8640,7560,5,2,2,2 28 | K,a1=28|a2=0|d1=28|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 29 | L,a1=28|a2=0|d1=28|d2=28|p1=28|p2=0,8640,7560,5,2,2,2 30 | M,a1=28|a2=28|d1=28|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 31 | N,a1=28|a2=28|d1=0|d2=0|p1=28|p2=28,8640,7560,5,2,2,2 32 | O,a1=0|a2=28|d1=28|d2=28|p1=0|p2=28,8640,7560,5,2,2,2 33 | P,a1=28|a2=0|d1=0|d2=0|p1=28|p2=28,8640,7560,5,2,2,2 34 | Q,a1=28|a2=28|d1=28|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 35 | R,a1=0|a2=28|d1=0|d2=28|p1=0|p2=28,8640,7560,5,2,2,2 36 | S,a1=28|a2=28|d1=28|d2=0|p1=28|p2=28,8640,7560,5,2,2,2 37 | T,a1=28|a2=0|d1=28|d2=0|p1=28|p2=0,8640,7560,5,2,2,2 38 | U,a1=0|a2=28|d1=0|d2=28|p1=0|p2=28,8640,7560,5,2,2,2 39 | V,a1=0|a2=28|d1=28|d2=0|p1=28|p2=28,8640,7560,5,2,2,2 40 | W,a1=28|a2=0|d1=28|d2=28|p1=0|p2=28,8640,7560,5,2,2,2 41 | X,a1=28|a2=0|d1=28|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 42 | Y,a1=0|a2=28|d1=28|d2=0|p1=28|p2=28,8640,7560,5,2,2,2 43 | Z,a1=0|a2=28|d1=28|d2=0|p1=28|p2=28,8640,7560,5,2,2,2 44 | AA,a1=28|a2=0|d1=28|d2=28|p1=0|p2=28,8640,7560,5,2,2,2 45 | AB,a1=0|a2=28|d1=28|d2=0|p1=28|p2=28,8640,7560,5,2,2,2 46 | AC,a1=28|a2=0|d1=28|d2=28|p1=28|p2=0,8640,7560,5,2,2,2 47 | AD,a1=0|a2=28|d1=0|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 48 | AE,a1=28|a2=28|d1=0|d2=28|p1=0|p2=0,8640,7560,5,2,2,2 49 | AF,a1=28|a2=0|d1=28|d2=28|p1=28|p2=28,8640,7560,5,2,2,2 50 | AG,a1=28|a2=28|d1=0|d2=0|p1=28|p2=0,8640,7560,5,2,2,2 51 | AH,a1=28|a2=28|d1=28|d2=28|p1=28|p2=0,8640,7560,5,2,2,2 52 | AI,a1=28|a2=28|d1=28|d2=28|p1=28|p2=0,8640,7560,5,2,2,2 53 | AJ,a1=0|a2=0|d1=28|d2=0|p1=0|p2=28,8640,7560,5,2,2,2 54 | AK,a1=0|a2=28|d1=28|d2=28|p1=28|p2=0,8640,7560,5,2,2,2 55 | AL,a1=28|a2=0|d1=28|d2=28|p1=0|p2=0,8640,7560,5,2,2,2 56 | AM,a1=28|a2=0|d1=0|d2=28|p1=28|p2=0,8640,7560,5,2,2,2 57 | AN,a1=28|a2=28|d1=28|d2=28|p1=0|p2=28,8640,7560,5,2,2,2 58 | AO,a1=0|a2=0|d1=0|d2=28|p1=28|p2=28,8640,7560,6,2,3,2 59 | AP,a1=28|a2=28|d1=28|d2=28|p1=0|p2=0,8640,7560,6,2,3,2 60 | AQ,a1=0|a2=0|d1=0|d2=28|p1=28|p2=28,8640,7560,6,2,3,2 61 | AR,a1=28|a2=28|d1=28|d2=0|p1=28|p2=28,8640,7560,6,2,3,2 62 | AS,a1=28|a2=0|d1=28|d2=28|p1=0|p2=28,8640,7560,6,2,3,2 63 | AT,a1=28|a2=28|d1=28|d2=28|p1=0|p2=28,8640,7560,6,2,3,2 64 | AU,a1=0|a2=28|d1=0|d2=28|p1=28|p2=28,8640,7560,6,2,3,2 65 | AV,a1=28|a2=28|d1=28|d2=28|p1=28|p2=0,8640,7560,6,2,3,2 66 | AW,a1=0|a2=0|d1=0|d2=28|p1=28|p2=28,8640,7560,6,2,3,2 67 | AX,a1=28|a2=28|d1=28|d2=28|p1=28|p2=28,8640,7560,6,2,3,2 68 | 69 | SECTION_DAYS_OFF 70 | # EmployeeID, DayIndexes (start at zero) 71 | A,10,11 72 | B,4,13 73 | C,13,14 74 | D,14,15 75 | E,5,10 76 | F,9,13 77 | G,14,15 78 | H,26,27 79 | I,22,23 80 | J,17,18 81 | K,1,10 82 | L,19,20 83 | M,13,14 84 | N,21,22 85 | O,16,24 86 | P,16,17 87 | Q,17,18 88 | R,9,16 89 | S,15,16 90 | T,2,23 91 | U,1,10 92 | V,1,12 93 | W,3,4 94 | X,3,4 95 | Y,8,25 96 | Z,18,19 97 | AA,25,26 98 | AB,8,12 99 | AC,1,24 100 | AD,10,11 101 | AE,9,27 102 | AF,7,8 103 | AG,8,9 104 | AH,25,26 105 | AI,14,24 106 | AJ,9,10 107 | AK,11,12 108 | AL,5,8 109 | AM,1,2 110 | AN,25,26 111 | AO,9,18 112 | AP,14,15 113 | AQ,6,15 114 | AR,13,19 115 | AS,20,26 116 | AT,2,8 117 | AU,12,13 118 | AV,8,16 119 | AW,5,6 120 | AX,22,23 121 | 122 | SECTION_SHIFT_ON_REQUESTS 123 | # EmployeeID, Day, ShiftID, Weight 124 | A,7,p1,3 125 | A,8,p1,3 126 | A,9,p1,3 127 | C,9,p1,1 128 | C,24,p2,1 129 | E,3,a1,1 130 | E,8,a2,1 131 | E,12,p1,1 132 | E,13,p1,1 133 | E,14,p1,1 134 | E,21,a2,1 135 | E,22,a2,1 136 | E,23,a2,1 137 | E,24,a2,1 138 | E,25,a2,1 139 | F,14,a2,3 140 | F,15,a2,3 141 | G,3,a1,3 142 | G,4,a1,3 143 | G,5,a1,3 144 | G,6,a1,3 145 | G,7,a1,3 146 | H,1,d2,3 147 | H,2,d2,3 148 | H,3,d2,3 149 | H,4,d2,3 150 | H,5,d2,3 151 | I,9,d2,3 152 | I,10,d2,3 153 | I,11,d2,3 154 | I,18,p1,1 155 | J,10,p2,1 156 | J,11,p2,1 157 | J,12,p2,1 158 | J,13,p2,1 159 | J,14,p2,1 160 | K,3,p2,2 161 | K,4,p2,2 162 | K,5,p2,2 163 | K,6,p2,2 164 | K,7,p2,2 165 | K,13,a1,3 166 | M,20,a2,3 167 | M,21,a2,3 168 | M,22,a2,3 169 | N,17,a2,1 170 | O,3,p2,3 171 | O,14,d2,2 172 | O,19,p2,2 173 | O,20,p2,2 174 | O,25,d2,1 175 | P,8,a1,2 176 | P,14,a1,1 177 | R,2,p2,3 178 | R,3,p2,3 179 | R,11,p2,2 180 | R,12,p2,2 181 | R,13,p2,2 182 | R,14,p2,2 183 | R,15,p2,2 184 | S,0,p1,3 185 | S,1,p1,3 186 | S,2,p1,3 187 | S,3,p1,3 188 | S,4,p1,3 189 | T,9,p1,1 190 | T,10,p1,1 191 | U,20,d2,1 192 | U,21,d2,1 193 | U,22,d2,1 194 | V,8,d1,1 195 | V,9,d1,1 196 | V,13,p2,2 197 | V,14,p2,2 198 | V,15,p2,2 199 | V,16,p2,2 200 | W,0,a1,2 201 | W,6,p2,2 202 | W,7,p2,2 203 | W,8,p2,2 204 | W,9,p2,2 205 | W,17,p2,3 206 | W,18,p2,3 207 | W,19,p2,3 208 | W,20,p2,3 209 | W,21,p2,3 210 | X,19,d1,3 211 | X,20,d1,3 212 | X,21,d1,3 213 | X,22,d1,3 214 | X,23,d1,3 215 | Y,3,p2,2 216 | Y,9,a2,2 217 | Y,10,a2,2 218 | Y,11,a2,2 219 | Y,12,a2,2 220 | Z,13,p1,1 221 | Z,14,p1,1 222 | AA,5,a1,1 223 | AA,9,a1,1 224 | AA,10,a1,1 225 | AA,11,a1,1 226 | AA,12,a1,1 227 | AA,13,a1,1 228 | AA,18,a1,1 229 | AA,19,a1,1 230 | AA,20,a1,1 231 | AA,21,a1,1 232 | AA,27,p2,1 233 | AB,23,a2,3 234 | AB,24,a2,3 235 | AB,25,a2,3 236 | AB,26,a2,3 237 | AB,27,a2,3 238 | AC,3,d1,2 239 | AC,4,d1,2 240 | AE,0,d2,3 241 | AE,1,d2,3 242 | AE,2,d2,3 243 | AE,10,a1,3 244 | AE,11,a1,3 245 | AF,0,p1,1 246 | AF,1,p1,1 247 | AF,2,p1,1 248 | AF,3,p1,1 249 | AF,4,p1,1 250 | AG,24,a2,2 251 | AH,3,d1,3 252 | AH,4,d1,3 253 | AH,5,d1,3 254 | AH,6,d1,3 255 | AH,7,d1,3 256 | AH,13,p1,3 257 | AH,14,p1,3 258 | AH,15,p1,3 259 | AH,16,p1,3 260 | AI,0,a2,2 261 | AI,1,a2,2 262 | AI,2,a2,2 263 | AI,15,a2,2 264 | AI,16,a2,2 265 | AI,17,a2,2 266 | AI,18,a2,2 267 | AK,23,p1,1 268 | AL,2,a1,1 269 | AL,14,a1,1 270 | AL,15,a1,1 271 | AL,16,a1,1 272 | AL,17,a1,1 273 | AL,18,a1,1 274 | AM,3,a1,2 275 | AM,4,a1,2 276 | AM,5,a1,2 277 | AM,14,a1,1 278 | AM,21,a1,2 279 | AM,22,a1,2 280 | AM,23,a1,2 281 | AN,10,a2,1 282 | AN,11,a2,1 283 | AN,12,a2,1 284 | AN,13,a2,1 285 | AO,3,d2,3 286 | AO,4,d2,3 287 | AP,0,a1,3 288 | AP,5,a1,3 289 | AP,6,a1,3 290 | AP,7,a1,3 291 | AP,8,a1,3 292 | AT,13,d2,3 293 | AT,14,d2,3 294 | AT,15,d2,3 295 | AT,16,d2,3 296 | AT,25,a1,1 297 | AT,26,a1,1 298 | AT,27,a1,1 299 | AU,16,p1,2 300 | AU,17,p1,2 301 | AU,18,p1,2 302 | AU,27,d2,1 303 | AV,1,d1,1 304 | AV,2,d1,1 305 | AV,3,d1,1 306 | AV,4,d1,1 307 | AV,5,d1,1 308 | AV,17,d1,2 309 | AV,18,d1,2 310 | AV,19,d1,2 311 | AV,20,d1,2 312 | AV,24,d1,1 313 | AV,25,d1,1 314 | AW,3,d2,1 315 | AW,18,p1,3 316 | AX,0,p2,2 317 | AX,1,p2,2 318 | AX,2,p2,2 319 | AX,3,p2,2 320 | AX,4,p2,2 321 | 322 | SECTION_SHIFT_OFF_REQUESTS 323 | # EmployeeID, Day, ShiftID, Weight 324 | A,15,p2,1 325 | A,16,p2,1 326 | A,20,a1,2 327 | A,21,a1,2 328 | A,22,a1,2 329 | A,26,p2,2 330 | A,27,p2,2 331 | B,14,p1,2 332 | B,15,p1,2 333 | B,16,p1,2 334 | B,17,p1,2 335 | C,16,a2,3 336 | C,17,a2,3 337 | C,18,a2,3 338 | C,19,a2,3 339 | D,19,d2,2 340 | D,20,d2,2 341 | D,21,d2,2 342 | D,22,d2,2 343 | D,23,d2,2 344 | G,25,a1,1 345 | H,20,d2,1 346 | H,21,d2,1 347 | H,25,d1,3 348 | I,1,a2,3 349 | I,2,a2,3 350 | I,3,a2,3 351 | I,4,a2,3 352 | I,5,a2,3 353 | J,2,p2,3 354 | J,3,p2,3 355 | J,4,p2,3 356 | J,21,p1,2 357 | J,22,p1,2 358 | J,23,p1,2 359 | J,24,p1,2 360 | K,20,p2,2 361 | K,21,p2,2 362 | K,22,p2,2 363 | L,10,d1,1 364 | L,11,d1,1 365 | L,12,d1,1 366 | L,13,d1,1 367 | L,14,d1,1 368 | L,21,d2,2 369 | L,22,d2,2 370 | L,23,d2,2 371 | L,24,d2,2 372 | L,25,d2,2 373 | M,2,p1,3 374 | M,3,p1,3 375 | M,4,p1,3 376 | M,5,p1,3 377 | M,9,p1,2 378 | M,10,p1,2 379 | M,11,p1,2 380 | M,12,p1,2 381 | N,8,p1,2 382 | N,9,p1,2 383 | N,10,p1,2 384 | N,11,p1,2 385 | N,12,p1,2 386 | P,18,p2,3 387 | P,19,p2,3 388 | P,20,p2,3 389 | P,21,p2,3 390 | P,22,p2,3 391 | Q,24,p1,2 392 | Q,25,p1,2 393 | Q,26,p1,2 394 | S,9,d1,2 395 | S,10,d1,2 396 | S,11,d1,2 397 | T,17,a1,1 398 | U,13,d2,2 399 | Y,21,d1,3 400 | Y,22,d1,3 401 | Y,23,d1,3 402 | Z,3,p1,3 403 | Z,4,p1,3 404 | Z,5,p1,3 405 | Z,6,p1,3 406 | Z,7,p1,3 407 | Z,25,a2,3 408 | Z,26,a2,3 409 | Z,27,a2,3 410 | AB,3,p1,3 411 | AB,4,p1,3 412 | AC,16,a1,2 413 | AC,17,a1,2 414 | AD,19,d2,1 415 | AD,20,d2,1 416 | AE,18,a2,3 417 | AE,19,a2,3 418 | AE,20,a2,3 419 | AG,13,a2,1 420 | AG,14,a2,1 421 | AG,15,a2,1 422 | AG,16,a2,1 423 | AK,13,a2,1 424 | AK,14,a2,1 425 | AK,15,a2,1 426 | AK,16,a2,1 427 | AN,19,d1,2 428 | AN,20,d1,2 429 | AN,21,d1,2 430 | AO,19,p2,1 431 | AO,20,p2,1 432 | AO,21,p2,1 433 | AO,22,p2,1 434 | AO,23,p2,1 435 | AP,23,d1,3 436 | AP,24,d1,3 437 | AP,25,d1,3 438 | AP,26,d1,3 439 | AQ,2,d2,2 440 | AQ,3,d2,2 441 | AQ,4,d2,2 442 | AQ,17,p2,2 443 | AQ,18,p2,2 444 | AQ,19,p2,2 445 | AQ,20,p2,2 446 | AQ,21,p2,2 447 | AR,0,a2,1 448 | AR,1,a2,1 449 | AR,2,a2,1 450 | AR,7,p2,2 451 | AR,14,a1,2 452 | AR,15,a1,2 453 | AR,16,a1,2 454 | AR,17,a1,2 455 | AR,18,a1,2 456 | AW,25,p1,2 457 | AW,26,p1,2 458 | AX,16,a1,1 459 | AX,17,a1,1 460 | AX,18,a1,1 461 | AX,19,a1,1 462 | AX,25,d2,1 463 | 464 | SECTION_COVER 465 | # Day, ShiftID, Requirement, Weight for under, Weight for over 466 | 0,a1,5,100,1 467 | 0,a2,3,100,1 468 | 0,d1,8,100,1 469 | 0,d2,3,100,1 470 | 0,p1,5,100,1 471 | 0,p2,4,100,1 472 | 1,a1,7,100,1 473 | 1,a2,3,100,1 474 | 1,d1,8,100,1 475 | 1,d2,3,100,1 476 | 1,p1,6,100,1 477 | 1,p2,5,100,1 478 | 2,a1,4,100,1 479 | 2,a2,4,100,1 480 | 2,d1,9,100,1 481 | 2,d2,3,100,1 482 | 2,p1,5,100,1 483 | 2,p2,2,100,1 484 | 3,a1,6,100,1 485 | 3,a2,3,100,1 486 | 3,d1,9,100,1 487 | 3,d2,3,100,1 488 | 3,p1,5,100,1 489 | 3,p2,4,100,1 490 | 4,a1,6,100,1 491 | 4,a2,3,100,1 492 | 4,d1,7,100,1 493 | 4,d2,2,100,1 494 | 4,p1,5,100,1 495 | 4,p2,3,100,1 496 | 5,a1,4,100,1 497 | 5,a2,3,100,1 498 | 5,d1,11,100,1 499 | 5,d2,3,100,1 500 | 5,p1,5,100,1 501 | 5,p2,5,100,1 502 | 6,a1,6,100,1 503 | 6,a2,2,100,1 504 | 6,d1,9,100,1 505 | 6,d2,4,100,1 506 | 6,p1,6,100,1 507 | 6,p2,3,100,1 508 | 7,a1,6,100,1 509 | 7,a2,2,100,1 510 | 7,d1,9,100,1 511 | 7,d2,3,100,1 512 | 7,p1,5,100,1 513 | 7,p2,3,100,1 514 | 8,a1,7,100,1 515 | 8,a2,3,100,1 516 | 8,d1,9,100,1 517 | 8,d2,2,100,1 518 | 8,p1,6,100,1 519 | 8,p2,2,100,1 520 | 9,a1,6,100,1 521 | 9,a2,4,100,1 522 | 9,d1,8,100,1 523 | 9,d2,3,100,1 524 | 9,p1,6,100,1 525 | 9,p2,3,100,1 526 | 10,a1,7,100,1 527 | 10,a2,2,100,1 528 | 10,d1,9,100,1 529 | 10,d2,3,100,1 530 | 10,p1,6,100,1 531 | 10,p2,4,100,1 532 | 11,a1,7,100,1 533 | 11,a2,3,100,1 534 | 11,d1,8,100,1 535 | 11,d2,2,100,1 536 | 11,p1,7,100,1 537 | 11,p2,3,100,1 538 | 12,a1,6,100,1 539 | 12,a2,3,100,1 540 | 12,d1,10,100,1 541 | 12,d2,5,100,1 542 | 12,p1,6,100,1 543 | 12,p2,3,100,1 544 | 13,a1,6,100,1 545 | 13,a2,3,100,1 546 | 13,d1,8,100,1 547 | 13,d2,5,100,1 548 | 13,p1,6,100,1 549 | 13,p2,4,100,1 550 | 14,a1,5,100,1 551 | 14,a2,3,100,1 552 | 14,d1,9,100,1 553 | 14,d2,3,100,1 554 | 14,p1,5,100,1 555 | 14,p2,3,100,1 556 | 15,a1,6,100,1 557 | 15,a2,3,100,1 558 | 15,d1,6,100,1 559 | 15,d2,3,100,1 560 | 15,p1,7,100,1 561 | 15,p2,3,100,1 562 | 16,a1,7,100,1 563 | 16,a2,3,100,1 564 | 16,d1,8,100,1 565 | 16,d2,2,100,1 566 | 16,p1,6,100,1 567 | 16,p2,3,100,1 568 | 17,a1,6,100,1 569 | 17,a2,3,100,1 570 | 17,d1,7,100,1 571 | 17,d2,2,100,1 572 | 17,p1,6,100,1 573 | 17,p2,2,100,1 574 | 18,a1,7,100,1 575 | 18,a2,3,100,1 576 | 18,d1,8,100,1 577 | 18,d2,4,100,1 578 | 18,p1,4,100,1 579 | 18,p2,5,100,1 580 | 19,a1,5,100,1 581 | 19,a2,4,100,1 582 | 19,d1,7,100,1 583 | 19,d2,3,100,1 584 | 19,p1,4,100,1 585 | 19,p2,3,100,1 586 | 20,a1,6,100,1 587 | 20,a2,3,100,1 588 | 20,d1,9,100,1 589 | 20,d2,3,100,1 590 | 20,p1,6,100,1 591 | 20,p2,3,100,1 592 | 21,a1,5,100,1 593 | 21,a2,3,100,1 594 | 21,d1,9,100,1 595 | 21,d2,3,100,1 596 | 21,p1,5,100,1 597 | 21,p2,4,100,1 598 | 22,a1,6,100,1 599 | 22,a2,3,100,1 600 | 22,d1,8,100,1 601 | 22,d2,4,100,1 602 | 22,p1,8,100,1 603 | 22,p2,3,100,1 604 | 23,a1,5,100,1 605 | 23,a2,2,100,1 606 | 23,d1,10,100,1 607 | 23,d2,3,100,1 608 | 23,p1,3,100,1 609 | 23,p2,3,100,1 610 | 24,a1,7,100,1 611 | 24,a2,4,100,1 612 | 24,d1,8,100,1 613 | 24,d2,2,100,1 614 | 24,p1,5,100,1 615 | 24,p2,3,100,1 616 | 25,a1,4,100,1 617 | 25,a2,5,100,1 618 | 25,d1,6,100,1 619 | 25,d2,4,100,1 620 | 25,p1,5,100,1 621 | 25,p2,4,100,1 622 | 26,a1,7,100,1 623 | 26,a2,2,100,1 624 | 26,d1,8,100,1 625 | 26,d2,3,100,1 626 | 26,p1,4,100,1 627 | 26,p2,4,100,1 628 | 27,a1,5,100,1 629 | 27,a2,2,100,1 630 | 27,d1,7,100,1 631 | 27,d2,3,100,1 632 | 27,p1,5,100,1 633 | 27,p2,2,100,1 634 | -------------------------------------------------------------------------------- /Benchmarks/Instance14.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 42 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | D,480, 11 | L,480,E|D 12 | N,600,E|D|L 13 | 14 | SECTION_STAFF 15 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 16 | A,E=21|D=42|L=21|N=5,12960,12000,5,2,2,3 17 | B,E=0|D=42|L=0|N=5,12960,12000,5,2,2,3 18 | C,E=21|D=42|L=21|N=5,12960,12000,5,2,2,3 19 | D,E=0|D=42|L=21|N=0,12960,12000,5,2,2,3 20 | E,E=21|D=42|L=21|N=5,12960,12000,5,2,2,3 21 | F,E=0|D=42|L=21|N=0,12960,12000,5,2,2,3 22 | G,E=0|D=42|L=21|N=5,12960,12000,5,2,2,3 23 | H,E=21|D=42|L=21|N=5,12960,12000,5,2,2,3 24 | I,E=21|D=42|L=0|N=5,12960,12000,5,2,2,3 25 | J,E=21|D=42|L=21|N=5,12960,12000,5,2,2,3 26 | K,E=21|D=42|L=21|N=0,12960,12000,5,2,2,3 27 | L,E=21|D=42|L=21|N=5,12960,12000,5,2,2,3 28 | M,E=0|D=42|L=0|N=0,12960,12000,5,2,2,3 29 | N,E=21|D=42|L=0|N=5,12960,12000,5,2,2,3 30 | O,E=0|D=42|L=21|N=0,12960,12000,5,2,2,3 31 | P,E=0|D=42|L=0|N=5,12960,12000,5,2,2,3 32 | Q,E=0|D=42|L=21|N=0,12960,12000,5,2,2,3 33 | R,E=21|D=42|L=0|N=5,12960,12000,5,2,2,3 34 | S,E=21|D=42|L=21|N=5,12960,12000,5,2,2,3 35 | T,E=0|D=42|L=21|N=0,12960,12000,5,2,2,3 36 | U,E=21|D=42|L=21|N=0,12960,12000,6,2,3,3 37 | V,E=21|D=42|L=21|N=0,12960,12000,6,2,3,3 38 | W,E=0|D=42|L=21|N=5,12960,12000,6,2,3,3 39 | X,E=21|D=42|L=0|N=5,12960,12000,6,2,3,3 40 | Y,E=21|D=42|L=0|N=0,12960,12000,6,2,3,3 41 | Z,E=21|D=42|L=0|N=5,12960,12000,6,2,3,3 42 | AA,E=0|D=42|L=21|N=5,12960,12000,6,2,3,3 43 | AB,E=10|D=42|L=0|N=0,6480,5520,5,1,2,3 44 | AC,E=10|D=42|L=10|N=2,6480,5520,5,1,2,3 45 | AD,E=10|D=42|L=10|N=2,6480,5520,5,1,2,3 46 | AE,E=10|D=42|L=10|N=2,6480,5520,5,1,2,3 47 | AF,E=10|D=42|L=0|N=0,6480,5520,5,1,2,3 48 | 49 | SECTION_DAYS_OFF 50 | # EmployeeID, DayIndexes (start at zero) 51 | A,14,15,16,17 52 | B,19,20,39,40 53 | C,11,16,17,40 54 | D,18,19,20,21 55 | E,17,18,19,20 56 | F,13,24,25,26 57 | G,10,11,39,40 58 | H,35,36,37,38 59 | I,26,27,28,31 60 | J,1,2,31,32 61 | K,3,4,5,6 62 | L,1,7,25,26 63 | M,23,24,25,26 64 | N,2,3,12,13 65 | O,14,15,16,17 66 | P,20,33,34,35 67 | Q,27,28,31,32 68 | R,29,30,31,32 69 | S,19,24,25,26 70 | T,28,31,32,33 71 | U,0,1,2,37 72 | V,7,30,34,35 73 | W,7,8,9,10 74 | X,1,30,31,32 75 | Y,0,10,11,12 76 | Z,11,36,37,38 77 | AA,26,39,40,41 78 | AB,14,24,25,32 79 | AC,34,35,36,37 80 | AD,17,18,19,20 81 | AE,6,8,21,25 82 | AF,7,8,9,10 83 | 84 | SECTION_SHIFT_ON_REQUESTS 85 | # EmployeeID, Day, ShiftID, Weight 86 | A,0,D,3 87 | A,19,E,2 88 | A,20,E,2 89 | A,21,E,2 90 | A,22,E,2 91 | A,36,L,3 92 | A,37,L,3 93 | A,38,L,3 94 | A,39,L,3 95 | B,9,N,2 96 | B,10,N,2 97 | B,11,N,2 98 | B,23,D,2 99 | B,24,D,2 100 | B,25,D,2 101 | B,26,D,2 102 | B,30,D,2 103 | B,31,D,2 104 | B,32,D,2 105 | B,33,D,2 106 | C,3,N,3 107 | C,4,N,3 108 | C,18,E,1 109 | C,19,E,1 110 | C,37,N,3 111 | D,12,L,2 112 | D,13,L,2 113 | E,0,L,1 114 | E,1,L,1 115 | E,5,D,2 116 | E,6,D,2 117 | E,12,L,1 118 | E,13,L,1 119 | E,14,L,1 120 | E,15,L,1 121 | E,16,L,1 122 | E,21,L,3 123 | E,22,L,3 124 | E,23,L,3 125 | E,24,L,3 126 | E,25,L,3 127 | F,0,D,3 128 | F,1,D,3 129 | F,2,D,3 130 | F,3,D,3 131 | F,15,L,3 132 | F,16,L,3 133 | F,17,L,3 134 | F,18,L,3 135 | F,27,L,3 136 | F,28,L,3 137 | F,29,L,3 138 | G,1,N,3 139 | G,32,N,3 140 | H,3,E,2 141 | H,4,E,2 142 | H,8,E,1 143 | H,9,E,1 144 | H,10,E,1 145 | H,11,E,1 146 | H,23,N,2 147 | H,24,N,2 148 | H,25,N,2 149 | I,5,N,2 150 | J,13,N,1 151 | J,14,N,1 152 | J,15,N,1 153 | J,22,E,1 154 | J,23,E,1 155 | J,27,D,1 156 | J,28,D,1 157 | J,29,D,1 158 | J,30,D,1 159 | J,34,N,2 160 | J,35,N,2 161 | J,36,N,2 162 | J,37,N,2 163 | J,38,N,2 164 | K,13,L,2 165 | K,14,L,2 166 | K,15,L,2 167 | K,16,L,2 168 | K,17,L,2 169 | K,21,L,2 170 | K,26,E,2 171 | K,27,E,2 172 | K,28,E,2 173 | K,29,E,2 174 | K,38,D,1 175 | L,2,E,3 176 | L,3,E,3 177 | L,4,E,3 178 | L,5,E,3 179 | L,6,E,3 180 | M,5,D,3 181 | M,6,D,3 182 | M,28,D,3 183 | M,29,D,3 184 | M,30,D,3 185 | M,31,D,3 186 | N,5,E,3 187 | N,6,E,3 188 | N,7,E,3 189 | N,34,N,2 190 | N,35,N,2 191 | O,11,L,1 192 | O,29,L,3 193 | O,30,L,3 194 | O,31,L,3 195 | O,32,L,3 196 | O,33,L,3 197 | P,2,N,3 198 | P,3,N,3 199 | P,8,D,3 200 | P,9,D,3 201 | P,10,D,3 202 | P,11,D,3 203 | P,12,D,3 204 | P,37,D,2 205 | Q,2,L,1 206 | Q,7,L,1 207 | Q,8,L,1 208 | Q,9,L,1 209 | Q,10,L,1 210 | Q,11,L,1 211 | R,2,E,1 212 | R,8,E,2 213 | R,9,E,2 214 | R,10,E,2 215 | R,11,E,2 216 | R,12,E,2 217 | R,21,D,2 218 | R,22,D,2 219 | R,23,D,2 220 | R,24,D,2 221 | R,37,N,1 222 | R,38,N,1 223 | S,4,N,2 224 | S,5,N,2 225 | S,6,N,2 226 | S,14,E,3 227 | S,15,E,3 228 | S,29,N,3 229 | S,30,N,3 230 | S,31,N,3 231 | S,32,N,3 232 | S,33,N,3 233 | T,1,D,2 234 | T,2,D,2 235 | T,3,D,2 236 | T,4,D,2 237 | T,5,D,2 238 | T,10,D,1 239 | T,11,D,1 240 | T,12,D,1 241 | T,17,L,1 242 | T,18,L,1 243 | T,19,L,1 244 | T,20,L,1 245 | T,21,L,1 246 | T,29,D,1 247 | T,34,L,2 248 | T,38,L,2 249 | T,39,L,2 250 | T,40,L,2 251 | T,41,L,2 252 | U,4,D,2 253 | U,5,D,2 254 | U,6,D,2 255 | U,7,D,2 256 | U,15,L,2 257 | U,16,L,2 258 | U,17,L,2 259 | U,18,L,2 260 | U,32,L,1 261 | U,33,L,1 262 | U,34,L,1 263 | U,35,L,1 264 | U,36,L,1 265 | V,1,L,1 266 | V,2,L,1 267 | V,19,E,2 268 | V,20,E,2 269 | V,21,E,2 270 | W,0,L,2 271 | W,1,L,2 272 | W,2,L,2 273 | W,3,L,2 274 | W,4,L,2 275 | W,13,D,1 276 | W,30,L,2 277 | W,31,L,2 278 | W,32,L,2 279 | W,33,L,2 280 | W,34,L,2 281 | X,0,N,3 282 | X,7,N,3 283 | X,8,N,3 284 | X,9,N,3 285 | X,10,N,3 286 | Y,1,E,3 287 | Y,2,E,3 288 | Z,1,E,1 289 | Z,2,E,1 290 | Z,3,E,1 291 | Z,4,E,1 292 | Z,13,D,1 293 | Z,14,D,1 294 | AA,2,L,2 295 | AA,3,L,2 296 | AA,4,L,2 297 | AA,5,L,2 298 | AA,9,L,2 299 | AA,10,L,2 300 | AA,11,L,2 301 | AA,12,L,2 302 | AA,19,N,2 303 | AA,20,N,2 304 | AA,21,N,2 305 | AA,22,N,2 306 | AA,30,L,2 307 | AA,31,L,2 308 | AA,32,L,2 309 | AA,33,L,2 310 | AA,34,L,2 311 | AB,17,E,1 312 | AB,18,E,1 313 | AB,19,E,1 314 | AB,20,E,1 315 | AB,26,E,3 316 | AB,27,E,3 317 | AB,28,E,3 318 | AB,29,E,3 319 | AC,2,N,1 320 | AC,3,N,1 321 | AC,30,D,2 322 | AC,31,D,2 323 | AC,32,D,2 324 | AD,2,E,1 325 | AD,22,N,1 326 | AD,23,N,1 327 | AD,24,N,1 328 | AD,25,N,1 329 | AD,26,N,1 330 | AD,32,D,3 331 | AD,33,D,3 332 | AE,9,D,2 333 | AE,10,D,2 334 | AE,11,D,2 335 | AE,12,D,2 336 | AE,13,D,2 337 | AE,36,E,3 338 | AE,37,E,3 339 | AE,38,E,3 340 | AE,39,E,3 341 | AE,40,E,3 342 | AF,17,E,3 343 | AF,18,E,3 344 | AF,19,E,3 345 | AF,20,E,3 346 | AF,26,D,1 347 | AF,27,D,1 348 | AF,31,E,1 349 | AF,32,E,1 350 | AF,33,E,1 351 | AF,39,E,3 352 | 353 | SECTION_SHIFT_OFF_REQUESTS 354 | # EmployeeID, Day, ShiftID, Weight 355 | A,8,L,3 356 | A,9,L,3 357 | A,10,L,3 358 | A,11,L,3 359 | B,0,D,2 360 | B,1,D,2 361 | B,2,D,2 362 | B,3,D,2 363 | B,4,D,2 364 | C,26,L,1 365 | C,27,L,1 366 | C,28,L,1 367 | C,29,L,1 368 | C,30,L,1 369 | D,3,D,2 370 | D,4,D,2 371 | D,5,D,2 372 | D,6,D,2 373 | D,29,L,1 374 | D,36,D,1 375 | D,37,D,1 376 | D,38,D,1 377 | E,32,N,2 378 | E,33,N,2 379 | E,34,N,2 380 | G,19,D,3 381 | I,0,E,1 382 | I,1,E,1 383 | I,16,N,1 384 | I,17,N,1 385 | I,18,N,1 386 | I,32,N,2 387 | I,33,N,2 388 | I,34,N,2 389 | I,35,N,2 390 | K,0,E,2 391 | L,10,E,2 392 | M,10,D,3 393 | M,14,D,1 394 | N,20,N,2 395 | N,21,N,2 396 | N,22,N,2 397 | N,29,D,2 398 | O,3,L,1 399 | P,24,N,3 400 | P,25,N,3 401 | P,26,N,3 402 | P,27,N,3 403 | P,28,N,3 404 | Q,16,L,3 405 | Q,17,L,3 406 | Q,18,L,3 407 | Q,19,L,3 408 | S,38,D,1 409 | U,24,D,1 410 | U,25,D,1 411 | W,19,D,2 412 | W,20,D,2 413 | W,21,D,2 414 | W,22,D,2 415 | W,23,D,2 416 | X,15,D,1 417 | X,16,D,1 418 | X,17,D,1 419 | X,25,D,1 420 | Y,33,E,3 421 | Y,34,E,3 422 | Y,35,E,3 423 | Y,36,E,3 424 | Z,27,D,3 425 | Z,28,D,3 426 | Z,29,D,3 427 | Z,30,D,3 428 | Z,31,D,3 429 | AB,0,D,3 430 | AB,1,D,3 431 | AB,2,D,3 432 | AB,10,E,2 433 | AC,11,E,2 434 | AC,12,E,2 435 | AC,13,E,2 436 | AC,14,E,2 437 | AC,15,E,2 438 | AD,9,L,1 439 | AD,10,L,1 440 | AD,11,L,1 441 | AD,12,L,1 442 | AD,13,L,1 443 | AE,18,D,2 444 | AE,19,D,2 445 | AF,2,D,2 446 | AF,3,D,2 447 | AF,4,D,2 448 | 449 | SECTION_COVER 450 | # Day, ShiftID, Requirement, Weight for under, Weight for over 451 | 0,E,6,100,1 452 | 0,D,5,100,1 453 | 0,L,6,100,1 454 | 0,N,3,100,1 455 | 1,E,6,100,1 456 | 1,D,4,100,1 457 | 1,L,5,100,1 458 | 1,N,3,100,1 459 | 2,E,5,100,1 460 | 2,D,7,100,1 461 | 2,L,3,100,1 462 | 2,N,3,100,1 463 | 3,E,6,100,1 464 | 3,D,6,100,1 465 | 3,L,5,100,1 466 | 3,N,5,100,1 467 | 4,E,6,100,1 468 | 4,D,6,100,1 469 | 4,L,5,100,1 470 | 4,N,2,100,1 471 | 5,E,5,100,1 472 | 5,D,5,100,1 473 | 5,L,5,100,1 474 | 5,N,3,100,1 475 | 6,E,3,100,1 476 | 6,D,5,100,1 477 | 6,L,4,100,1 478 | 6,N,4,100,1 479 | 7,E,4,100,1 480 | 7,D,5,100,1 481 | 7,L,3,100,1 482 | 7,N,3,100,1 483 | 8,E,5,100,1 484 | 8,D,5,100,1 485 | 8,L,5,100,1 486 | 8,N,2,100,1 487 | 9,E,5,100,1 488 | 9,D,5,100,1 489 | 9,L,5,100,1 490 | 9,N,3,100,1 491 | 10,E,6,100,1 492 | 10,D,5,100,1 493 | 10,L,6,100,1 494 | 10,N,3,100,1 495 | 11,E,7,100,1 496 | 11,D,4,100,1 497 | 11,L,4,100,1 498 | 11,N,3,100,1 499 | 12,E,4,100,1 500 | 12,D,6,100,1 501 | 12,L,5,100,1 502 | 12,N,4,100,1 503 | 13,E,5,100,1 504 | 13,D,5,100,1 505 | 13,L,6,100,1 506 | 13,N,3,100,1 507 | 14,E,4,100,1 508 | 14,D,7,100,1 509 | 14,L,7,100,1 510 | 14,N,3,100,1 511 | 15,E,5,100,1 512 | 15,D,6,100,1 513 | 15,L,3,100,1 514 | 15,N,3,100,1 515 | 16,E,5,100,1 516 | 16,D,5,100,1 517 | 16,L,7,100,1 518 | 16,N,4,100,1 519 | 17,E,4,100,1 520 | 17,D,5,100,1 521 | 17,L,6,100,1 522 | 17,N,2,100,1 523 | 18,E,4,100,1 524 | 18,D,5,100,1 525 | 18,L,4,100,1 526 | 18,N,2,100,1 527 | 19,E,5,100,1 528 | 19,D,5,100,1 529 | 19,L,5,100,1 530 | 19,N,2,100,1 531 | 20,E,4,100,1 532 | 20,D,5,100,1 533 | 20,L,6,100,1 534 | 20,N,2,100,1 535 | 21,E,6,100,1 536 | 21,D,4,100,1 537 | 21,L,5,100,1 538 | 21,N,2,100,1 539 | 22,E,3,100,1 540 | 22,D,5,100,1 541 | 22,L,4,100,1 542 | 22,N,1,100,1 543 | 23,E,6,100,1 544 | 23,D,6,100,1 545 | 23,L,5,100,1 546 | 23,N,3,100,1 547 | 24,E,3,100,1 548 | 24,D,5,100,1 549 | 24,L,4,100,1 550 | 24,N,1,100,1 551 | 25,E,2,100,1 552 | 25,D,6,100,1 553 | 25,L,2,100,1 554 | 25,N,1,100,1 555 | 26,E,7,100,1 556 | 26,D,3,100,1 557 | 26,L,5,100,1 558 | 26,N,1,100,1 559 | 27,E,4,100,1 560 | 27,D,5,100,1 561 | 27,L,5,100,1 562 | 27,N,2,100,1 563 | 28,E,5,100,1 564 | 28,D,4,100,1 565 | 28,L,5,100,1 566 | 28,N,2,100,1 567 | 29,E,4,100,1 568 | 29,D,4,100,1 569 | 29,L,4,100,1 570 | 29,N,3,100,1 571 | 30,E,3,100,1 572 | 30,D,4,100,1 573 | 30,L,5,100,1 574 | 30,N,1,100,1 575 | 31,E,6,100,1 576 | 31,D,3,100,1 577 | 31,L,5,100,1 578 | 31,N,2,100,1 579 | 32,E,5,100,1 580 | 32,D,4,100,1 581 | 32,L,4,100,1 582 | 32,N,1,100,1 583 | 33,E,2,100,1 584 | 33,D,6,100,1 585 | 33,L,4,100,1 586 | 33,N,2,100,1 587 | 34,E,6,100,1 588 | 34,D,4,100,1 589 | 34,L,4,100,1 590 | 34,N,2,100,1 591 | 35,E,4,100,1 592 | 35,D,4,100,1 593 | 35,L,4,100,1 594 | 35,N,2,100,1 595 | 36,E,5,100,1 596 | 36,D,4,100,1 597 | 36,L,4,100,1 598 | 36,N,2,100,1 599 | 37,E,4,100,1 600 | 37,D,5,100,1 601 | 37,L,3,100,1 602 | 37,N,2,100,1 603 | 38,E,4,100,1 604 | 38,D,2,100,1 605 | 38,L,4,100,1 606 | 38,N,2,100,1 607 | 39,E,5,100,1 608 | 39,D,6,100,1 609 | 39,L,6,100,1 610 | 39,N,3,100,1 611 | 40,E,6,100,1 612 | 40,D,4,100,1 613 | 40,L,4,100,1 614 | 40,N,4,100,1 615 | 41,E,1,100,1 616 | 41,D,3,100,1 617 | 41,L,1,100,1 618 | 41,N,2,100,1 619 | -------------------------------------------------------------------------------- /Benchmarks/Instance16.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 56 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | D,480,E 11 | L,480,E|D 12 | 13 | SECTION_STAFF 14 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 15 | A,E=56|D=56|L=56,16860,16380,5,2,2,4 16 | B,E=56|D=56|L=56,16860,16380,5,2,2,4 17 | C,E=56|D=0|L=56,16860,16380,5,2,2,4 18 | D,E=56|D=56|L=0,16860,16380,5,2,2,4 19 | E,E=0|D=56|L=56,16860,16380,5,2,2,4 20 | F,E=56|D=56|L=56,16860,16380,5,2,2,4 21 | G,E=0|D=0|L=56,16860,16380,5,2,2,4 22 | H,E=56|D=56|L=56,16860,16380,5,2,2,4 23 | I,E=0|D=56|L=0,16860,16380,5,2,2,4 24 | J,E=0|D=56|L=56,16860,16380,5,2,2,4 25 | K,E=56|D=56|L=56,16860,16380,5,2,2,4 26 | L,E=0|D=56|L=0,16860,16380,5,2,2,4 27 | M,E=0|D=0|L=56,16860,16380,5,2,2,4 28 | N,E=56|D=56|L=56,16860,16380,5,2,2,4 29 | O,E=56|D=56|L=56,16860,16380,5,2,2,4 30 | P,E=0|D=56|L=56,16860,16380,5,2,2,4 31 | Q,E=0|D=56|L=0,16860,16380,5,2,2,4 32 | R,E=56|D=56|L=56,16860,16380,5,2,2,4 33 | S,E=56|D=56|L=56,16860,16380,5,2,2,4 34 | T,E=56|D=56|L=56,16860,16380,5,2,2,4 35 | 36 | SECTION_DAYS_OFF 37 | # EmployeeID, DayIndexes (start at zero) 38 | A,32,33,34,35,52,53 39 | B,2,3,4,5,6,49 40 | C,23,24,25,26,27,33 41 | D,15,16,17,18,19,20 42 | E,6,7,8,9,10,11 43 | F,37,38,39,40,41,42 44 | G,32,33,34,35,54,55 45 | H,15,16,17,23,24,25 46 | I,17,18,19,20,21,22 47 | J,34,35,36,37,38,39 48 | K,23,24,25,26,27,28 49 | L,15,16,17,23,26,37 50 | M,46,47,48,49,50,51 51 | N,27,28,29,41,42,55 52 | O,13,26,27,28,29,30 53 | P,31,32,44,49,50,51 54 | Q,18,19,20,21,22,23 55 | R,2,10,11,26,27,35 56 | S,16,17,18,36,37,38 57 | T,11,17,18,19,28,48 58 | 59 | SECTION_SHIFT_ON_REQUESTS 60 | # EmployeeID, Day, ShiftID, Weight 61 | A,20,D,3 62 | A,21,D,3 63 | A,22,D,3 64 | A,23,D,3 65 | A,24,D,3 66 | A,51,D,2 67 | B,1,D,3 68 | B,7,E,1 69 | B,8,E,1 70 | B,16,L,3 71 | B,17,L,3 72 | B,18,L,3 73 | B,22,L,2 74 | B,26,L,1 75 | B,27,L,1 76 | B,28,L,1 77 | B,29,L,1 78 | B,30,L,1 79 | B,36,E,1 80 | B,37,E,1 81 | B,38,E,1 82 | B,39,E,1 83 | B,44,L,2 84 | B,45,L,2 85 | B,46,L,2 86 | B,47,L,2 87 | C,13,E,2 88 | C,14,E,2 89 | C,15,E,2 90 | C,16,E,2 91 | C,35,E,3 92 | C,36,E,3 93 | C,43,E,2 94 | C,47,L,2 95 | C,48,L,2 96 | C,49,L,2 97 | C,50,L,2 98 | D,1,E,2 99 | D,2,E,2 100 | D,3,E,2 101 | D,11,D,3 102 | D,12,D,3 103 | D,39,D,1 104 | D,40,D,1 105 | D,41,D,1 106 | D,45,D,3 107 | D,46,D,3 108 | D,47,D,3 109 | D,48,D,3 110 | E,15,L,1 111 | E,16,L,1 112 | E,17,L,1 113 | E,18,L,1 114 | E,26,D,3 115 | E,27,D,3 116 | E,28,D,3 117 | F,23,E,1 118 | F,24,E,1 119 | F,51,E,2 120 | F,52,E,2 121 | G,1,L,3 122 | G,2,L,3 123 | G,3,L,3 124 | G,4,L,3 125 | G,5,L,3 126 | G,24,L,1 127 | G,25,L,1 128 | G,44,L,1 129 | G,52,L,2 130 | G,53,L,2 131 | H,8,L,3 132 | H,9,L,3 133 | H,19,L,1 134 | H,20,L,1 135 | H,21,L,1 136 | H,22,L,1 137 | H,31,E,1 138 | H,32,E,1 139 | H,37,E,1 140 | H,42,L,2 141 | I,37,D,1 142 | J,45,L,2 143 | J,46,L,2 144 | J,47,L,2 145 | J,48,L,2 146 | J,53,L,1 147 | K,0,E,2 148 | K,35,E,2 149 | K,36,E,2 150 | L,3,D,3 151 | L,4,D,3 152 | L,5,D,3 153 | L,6,D,3 154 | L,7,D,3 155 | L,18,D,3 156 | L,19,D,3 157 | L,20,D,3 158 | L,27,D,1 159 | L,28,D,1 160 | L,47,D,2 161 | L,48,D,2 162 | L,49,D,2 163 | L,50,D,2 164 | L,51,D,2 165 | L,55,D,1 166 | M,16,L,3 167 | M,27,L,3 168 | M,31,L,3 169 | M,32,L,3 170 | M,33,L,3 171 | M,34,L,3 172 | M,41,L,1 173 | M,42,L,1 174 | M,43,L,1 175 | M,44,L,1 176 | M,52,L,1 177 | M,53,L,1 178 | N,7,L,3 179 | N,8,L,3 180 | N,9,L,3 181 | O,6,L,1 182 | O,7,L,1 183 | O,8,L,1 184 | O,9,L,1 185 | O,22,L,1 186 | O,23,L,1 187 | O,24,L,1 188 | O,31,D,1 189 | O,32,D,1 190 | O,33,D,1 191 | O,41,E,1 192 | O,42,E,1 193 | O,43,E,1 194 | O,47,L,1 195 | P,8,D,1 196 | P,9,D,1 197 | P,10,D,1 198 | P,11,D,1 199 | P,12,D,1 200 | P,20,L,2 201 | P,21,L,2 202 | P,22,L,2 203 | P,29,L,2 204 | P,30,L,2 205 | P,38,L,2 206 | P,39,L,2 207 | P,40,L,2 208 | Q,24,D,1 209 | R,13,D,1 210 | R,14,D,1 211 | R,21,E,1 212 | R,22,E,1 213 | R,30,E,3 214 | R,31,E,3 215 | R,32,E,3 216 | R,33,E,3 217 | R,34,E,3 218 | R,38,D,3 219 | R,39,D,3 220 | R,40,D,3 221 | R,41,D,3 222 | R,42,D,3 223 | S,30,L,3 224 | S,31,L,3 225 | S,44,E,1 226 | S,45,E,1 227 | S,46,E,1 228 | T,13,E,2 229 | T,25,L,2 230 | T,26,L,2 231 | T,33,D,1 232 | T,34,D,1 233 | T,39,E,1 234 | T,40,E,1 235 | T,52,D,2 236 | T,53,D,2 237 | T,54,D,2 238 | 239 | SECTION_SHIFT_OFF_REQUESTS 240 | # EmployeeID, Day, ShiftID, Weight 241 | A,1,D,2 242 | A,2,D,2 243 | A,12,L,1 244 | A,40,L,1 245 | A,41,L,1 246 | A,42,L,1 247 | A,43,L,1 248 | A,47,E,1 249 | C,21,E,1 250 | C,22,E,1 251 | D,30,E,2 252 | D,31,E,2 253 | D,32,E,2 254 | E,32,L,2 255 | E,33,L,2 256 | E,34,L,2 257 | E,35,L,2 258 | E,42,L,3 259 | E,43,L,3 260 | E,44,L,3 261 | E,45,L,3 262 | E,46,L,3 263 | E,52,D,1 264 | E,53,D,1 265 | F,0,E,3 266 | F,1,E,3 267 | F,2,E,3 268 | F,3,E,3 269 | F,4,E,3 270 | F,8,L,3 271 | F,9,L,3 272 | F,10,L,3 273 | F,11,L,3 274 | F,32,L,3 275 | H,50,E,3 276 | H,51,E,3 277 | H,52,E,3 278 | H,53,E,3 279 | H,54,E,3 280 | I,24,D,1 281 | I,25,D,1 282 | I,26,D,1 283 | J,21,L,2 284 | J,22,L,2 285 | J,23,L,2 286 | J,24,L,2 287 | J,25,L,2 288 | K,46,E,2 289 | K,47,E,2 290 | K,48,E,2 291 | K,49,E,2 292 | M,20,L,2 293 | N,2,L,2 294 | N,19,D,1 295 | N,20,D,1 296 | N,21,D,1 297 | N,34,L,1 298 | N,35,L,1 299 | N,36,L,1 300 | N,37,L,1 301 | N,38,L,1 302 | N,44,D,3 303 | N,45,D,3 304 | N,46,D,3 305 | N,47,D,3 306 | N,48,D,3 307 | O,0,L,1 308 | O,1,L,1 309 | O,52,E,2 310 | P,0,L,2 311 | P,1,L,2 312 | P,2,L,2 313 | P,3,L,2 314 | Q,0,D,2 315 | Q,1,D,2 316 | Q,2,D,2 317 | Q,3,D,2 318 | Q,4,D,2 319 | Q,37,D,3 320 | Q,38,D,3 321 | Q,39,D,3 322 | Q,40,D,3 323 | Q,41,D,3 324 | R,4,E,2 325 | R,5,E,2 326 | S,2,D,1 327 | S,3,D,1 328 | S,4,D,1 329 | S,12,L,3 330 | S,24,E,1 331 | S,51,E,3 332 | S,52,E,3 333 | S,53,E,3 334 | S,54,E,3 335 | S,55,E,3 336 | T,2,L,2 337 | T,3,L,2 338 | T,4,L,2 339 | T,5,L,2 340 | T,6,L,2 341 | T,45,L,3 342 | T,46,L,3 343 | T,47,L,3 344 | 345 | SECTION_COVER 346 | # Day, ShiftID, Requirement, Weight for under, Weight for over 347 | 0,E,5,100,1 348 | 0,D,5,100,1 349 | 0,L,5,100,1 350 | 1,E,4,100,1 351 | 1,D,7,100,1 352 | 1,L,4,100,1 353 | 2,E,4,100,1 354 | 2,D,5,100,1 355 | 2,L,3,100,1 356 | 3,E,6,100,1 357 | 3,D,5,100,1 358 | 3,L,2,100,1 359 | 4,E,3,100,1 360 | 4,D,4,100,1 361 | 4,L,2,100,1 362 | 5,E,4,100,1 363 | 5,D,4,100,1 364 | 5,L,4,100,1 365 | 6,E,2,100,1 366 | 6,D,5,100,1 367 | 6,L,5,100,1 368 | 7,E,5,100,1 369 | 7,D,5,100,1 370 | 7,L,4,100,1 371 | 8,E,4,100,1 372 | 8,D,5,100,1 373 | 8,L,2,100,1 374 | 9,E,4,100,1 375 | 9,D,4,100,1 376 | 9,L,4,100,1 377 | 10,E,4,100,1 378 | 10,D,4,100,1 379 | 10,L,5,100,1 380 | 11,E,6,100,1 381 | 11,D,5,100,1 382 | 11,L,4,100,1 383 | 12,E,4,100,1 384 | 12,D,7,100,1 385 | 12,L,4,100,1 386 | 13,E,3,100,1 387 | 13,D,5,100,1 388 | 13,L,4,100,1 389 | 14,E,3,100,1 390 | 14,D,6,100,1 391 | 14,L,3,100,1 392 | 15,E,4,100,1 393 | 15,D,5,100,1 394 | 15,L,3,100,1 395 | 16,E,3,100,1 396 | 16,D,6,100,1 397 | 16,L,3,100,1 398 | 17,E,3,100,1 399 | 17,D,5,100,1 400 | 17,L,3,100,1 401 | 18,E,4,100,1 402 | 18,D,4,100,1 403 | 18,L,3,100,1 404 | 19,E,2,100,1 405 | 19,D,6,100,1 406 | 19,L,4,100,1 407 | 20,E,4,100,1 408 | 20,D,5,100,1 409 | 20,L,3,100,1 410 | 21,E,3,100,1 411 | 21,D,5,100,1 412 | 21,L,5,100,1 413 | 22,E,2,100,1 414 | 22,D,4,100,1 415 | 22,L,3,100,1 416 | 23,E,4,100,1 417 | 23,D,4,100,1 418 | 23,L,3,100,1 419 | 24,E,4,100,1 420 | 24,D,5,100,1 421 | 24,L,3,100,1 422 | 25,E,3,100,1 423 | 25,D,3,100,1 424 | 25,L,3,100,1 425 | 26,E,4,100,1 426 | 26,D,5,100,1 427 | 26,L,4,100,1 428 | 27,E,4,100,1 429 | 27,D,5,100,1 430 | 27,L,4,100,1 431 | 28,E,3,100,1 432 | 28,D,4,100,1 433 | 28,L,3,100,1 434 | 29,E,4,100,1 435 | 29,D,5,100,1 436 | 29,L,4,100,1 437 | 30,E,3,100,1 438 | 30,D,5,100,1 439 | 30,L,4,100,1 440 | 31,E,3,100,1 441 | 31,D,7,100,1 442 | 31,L,2,100,1 443 | 32,E,3,100,1 444 | 32,D,5,100,1 445 | 32,L,3,100,1 446 | 33,E,3,100,1 447 | 33,D,5,100,1 448 | 33,L,4,100,1 449 | 34,E,2,100,1 450 | 34,D,5,100,1 451 | 34,L,3,100,1 452 | 35,E,4,100,1 453 | 35,D,3,100,1 454 | 35,L,4,100,1 455 | 36,E,4,100,1 456 | 36,D,5,100,1 457 | 36,L,4,100,1 458 | 37,E,5,100,1 459 | 37,D,6,100,1 460 | 37,L,3,100,1 461 | 38,E,4,100,1 462 | 38,D,5,100,1 463 | 38,L,4,100,1 464 | 39,E,3,100,1 465 | 39,D,5,100,1 466 | 39,L,4,100,1 467 | 40,E,2,100,1 468 | 40,D,5,100,1 469 | 40,L,3,100,1 470 | 41,E,3,100,1 471 | 41,D,5,100,1 472 | 41,L,3,100,1 473 | 42,E,3,100,1 474 | 42,D,6,100,1 475 | 42,L,2,100,1 476 | 43,E,3,100,1 477 | 43,D,4,100,1 478 | 43,L,2,100,1 479 | 44,E,5,100,1 480 | 44,D,5,100,1 481 | 44,L,4,100,1 482 | 45,E,2,100,1 483 | 45,D,4,100,1 484 | 45,L,2,100,1 485 | 46,E,4,100,1 486 | 46,D,5,100,1 487 | 46,L,3,100,1 488 | 47,E,2,100,1 489 | 47,D,5,100,1 490 | 47,L,4,100,1 491 | 48,E,4,100,1 492 | 48,D,5,100,1 493 | 48,L,3,100,1 494 | 49,E,4,100,1 495 | 49,D,5,100,1 496 | 49,L,6,100,1 497 | 50,E,3,100,1 498 | 50,D,3,100,1 499 | 50,L,5,100,1 500 | 51,E,4,100,1 501 | 51,D,4,100,1 502 | 51,L,4,100,1 503 | 52,E,3,100,1 504 | 52,D,4,100,1 505 | 52,L,3,100,1 506 | 53,E,5,100,1 507 | 53,D,5,100,1 508 | 53,L,6,100,1 509 | 54,E,3,100,1 510 | 54,D,2,100,1 511 | 54,L,3,100,1 512 | 55,E,5,100,1 513 | 55,D,5,100,1 514 | 55,L,6,100,1 515 | -------------------------------------------------------------------------------- /Benchmarks/Instance17.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 56 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | D,480,E 11 | L,480,E|D 12 | N,480,E|D|L 13 | 14 | SECTION_STAFF 15 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 16 | A,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 17 | B,E=56|D=0|L=56|N=0,17280,16800,5,2,2,4 18 | C,E=56|D=56|L=0|N=10,17280,16800,5,2,2,4 19 | D,E=56|D=56|L=56|N=0,17280,16800,5,2,2,4 20 | E,E=56|D=56|L=0|N=10,17280,16800,5,2,2,4 21 | F,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 22 | G,E=56|D=0|L=56|N=10,17280,16800,5,2,2,4 23 | H,E=56|D=0|L=56|N=0,17280,16800,5,2,2,4 24 | I,E=56|D=56|L=0|N=0,17280,16800,5,2,2,4 25 | J,E=56|D=56|L=56|N=0,17280,16800,5,2,2,4 26 | K,E=56|D=56|L=0|N=10,17280,16800,5,2,2,4 27 | L,E=56|D=0|L=56|N=10,17280,16800,5,2,2,4 28 | M,E=56|D=56|L=56|N=0,17280,16800,5,2,2,4 29 | N,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 30 | O,E=56|D=0|L=0|N=10,17280,16800,5,2,2,4 31 | P,E=0|D=56|L=56|N=10,17280,16800,5,2,2,4 32 | Q,E=56|D=0|L=56|N=10,17280,16800,5,2,2,4 33 | R,E=56|D=56|L=0|N=10,17280,16800,5,2,2,4 34 | S,E=56|D=56|L=56|N=0,17280,16800,5,2,2,4 35 | T,E=56|D=0|L=56|N=10,17280,16800,5,2,2,4 36 | U,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 37 | V,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 38 | W,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 39 | X,E=56|D=56|L=56|N=0,17280,16800,5,2,2,4 40 | Y,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 41 | Z,E=56|D=0|L=0|N=10,17280,16800,5,2,2,4 42 | AA,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 43 | AB,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 44 | AC,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 45 | AD,E=56|D=56|L=56|N=10,17280,16800,5,2,2,4 46 | AE,E=56|D=56|L=56|N=10,16200,15720,4,2,2,4 47 | AF,E=56|D=56|L=56|N=10,16200,15720,4,2,2,4 48 | 49 | SECTION_DAYS_OFF 50 | # EmployeeID, DayIndexes (start at zero) 51 | A,24,25,35,36,37 52 | B,0,1,25,39,52 53 | C,13,14,38,49,50 54 | D,34,35,36,37,38 55 | E,19,20,31,32,48 56 | F,6,7,8,33,53 57 | G,14,15,16,43,44 58 | H,5,6,7,15,34 59 | I,19,20,21,48,49 60 | J,18,19,20,21,24 61 | K,3,4,15,41,42 62 | L,27,28,29,30,40 63 | M,20,21,52,53,54 64 | N,4,5,6,7,12 65 | O,2,3,4,5,6 66 | P,31,44,45,46,47 67 | Q,2,6,7,43,44 68 | R,48,49,50,51,52 69 | S,21,22,23,37,38 70 | T,8,9,10,11,20 71 | U,5,6,7,41,42 72 | V,5,29,30,31,32 73 | W,6,7,8,9,10 74 | X,9,10,11,12,13 75 | Y,24,25,26,27,28 76 | Z,4,22,41,42,43 77 | AA,10,25,26,27,35 78 | AB,1,2,3,53,54 79 | AC,32,33,34,35,36 80 | AD,25,26,40,41,42 81 | AE,4,5,6,7,34 82 | AF,27,28,29,52,53 83 | 84 | SECTION_SHIFT_ON_REQUESTS 85 | # EmployeeID, Day, ShiftID, Weight 86 | A,41,E,3 87 | A,49,D,2 88 | A,50,D,2 89 | A,51,D,2 90 | A,52,D,2 91 | A,53,D,2 92 | B,15,E,3 93 | B,16,E,3 94 | B,41,E,2 95 | B,42,E,2 96 | B,43,E,2 97 | B,44,E,2 98 | B,45,E,2 99 | B,53,E,2 100 | B,54,E,2 101 | C,22,N,2 102 | C,23,N,2 103 | C,24,N,2 104 | C,25,N,2 105 | C,39,N,3 106 | C,40,N,3 107 | C,41,N,3 108 | C,42,N,3 109 | D,7,L,3 110 | D,8,L,3 111 | D,24,E,2 112 | D,51,D,1 113 | D,52,D,1 114 | D,53,D,1 115 | E,11,D,1 116 | E,12,D,1 117 | E,13,D,1 118 | E,14,D,1 119 | E,15,D,1 120 | E,22,N,1 121 | E,34,N,1 122 | E,35,N,1 123 | E,36,N,1 124 | E,53,N,2 125 | F,0,L,2 126 | F,1,L,2 127 | F,5,D,1 128 | F,13,E,1 129 | F,14,E,1 130 | F,15,E,1 131 | G,17,L,1 132 | G,21,L,1 133 | G,22,L,1 134 | G,23,L,1 135 | G,24,L,1 136 | G,25,L,1 137 | G,46,E,2 138 | G,47,E,2 139 | G,48,E,2 140 | G,49,E,2 141 | G,50,E,2 142 | H,17,L,1 143 | H,18,L,1 144 | H,19,L,1 145 | H,20,L,1 146 | H,21,L,1 147 | H,32,L,3 148 | H,33,L,3 149 | H,38,L,3 150 | H,39,L,3 151 | H,40,L,3 152 | H,41,L,3 153 | H,42,L,3 154 | I,2,E,1 155 | I,3,E,1 156 | I,4,E,1 157 | I,10,D,3 158 | I,11,D,3 159 | I,12,D,3 160 | I,13,D,3 161 | I,23,E,1 162 | I,24,E,1 163 | I,25,E,1 164 | I,51,E,2 165 | I,52,E,2 166 | I,53,E,2 167 | I,54,E,2 168 | I,55,E,2 169 | J,3,L,1 170 | J,12,E,2 171 | J,13,E,2 172 | J,42,E,3 173 | J,49,D,2 174 | J,50,D,2 175 | K,5,D,1 176 | K,17,N,3 177 | K,18,N,3 178 | K,23,D,1 179 | K,24,D,1 180 | K,28,E,2 181 | K,29,E,2 182 | K,30,E,2 183 | K,31,E,2 184 | K,50,N,2 185 | K,51,N,2 186 | K,52,N,2 187 | K,53,N,2 188 | K,54,N,2 189 | L,41,N,1 190 | L,42,N,1 191 | L,43,N,1 192 | L,44,N,1 193 | L,49,L,1 194 | L,50,L,1 195 | L,51,L,1 196 | M,3,E,1 197 | M,4,E,1 198 | M,12,L,2 199 | M,13,L,2 200 | M,14,L,2 201 | M,15,L,2 202 | M,41,D,2 203 | M,42,D,2 204 | M,47,L,3 205 | M,48,L,3 206 | M,49,L,3 207 | N,15,D,3 208 | N,16,D,3 209 | N,22,L,1 210 | N,23,L,1 211 | N,24,L,1 212 | N,25,L,1 213 | N,26,L,1 214 | N,31,L,1 215 | N,32,L,1 216 | N,33,L,1 217 | N,34,L,1 218 | N,35,L,1 219 | O,33,E,1 220 | O,34,E,1 221 | O,35,E,1 222 | O,36,E,1 223 | O,42,N,1 224 | O,43,N,1 225 | O,44,N,1 226 | O,45,N,1 227 | O,46,N,1 228 | O,50,E,2 229 | O,51,E,2 230 | P,0,D,1 231 | P,1,D,1 232 | P,2,D,1 233 | P,3,D,1 234 | P,8,D,3 235 | P,9,D,3 236 | P,10,D,3 237 | P,11,D,3 238 | P,22,L,3 239 | P,23,L,3 240 | P,24,L,3 241 | P,25,L,3 242 | Q,19,E,2 243 | Q,20,E,2 244 | Q,27,E,2 245 | Q,28,E,2 246 | Q,29,E,2 247 | Q,30,E,2 248 | Q,45,L,3 249 | Q,46,L,3 250 | R,0,D,3 251 | R,1,D,3 252 | R,2,D,3 253 | R,7,E,3 254 | R,15,N,2 255 | R,16,N,2 256 | R,17,N,2 257 | R,25,N,1 258 | R,26,N,1 259 | R,27,N,1 260 | R,31,E,1 261 | R,32,E,1 262 | R,33,E,1 263 | R,34,E,1 264 | R,35,E,1 265 | S,17,E,3 266 | S,18,E,3 267 | S,25,L,3 268 | S,26,L,3 269 | S,27,L,3 270 | T,0,E,2 271 | T,1,E,2 272 | T,2,E,2 273 | T,12,N,2 274 | T,13,N,2 275 | T,14,N,2 276 | T,29,N,2 277 | T,30,N,2 278 | U,12,L,1 279 | U,13,L,1 280 | U,14,L,1 281 | U,15,L,1 282 | U,29,E,3 283 | U,34,L,2 284 | U,35,L,2 285 | U,36,L,2 286 | U,40,N,2 287 | U,44,E,1 288 | U,45,E,1 289 | U,46,E,1 290 | U,47,E,1 291 | U,48,E,1 292 | V,11,N,1 293 | V,12,N,1 294 | V,16,E,1 295 | V,17,E,1 296 | V,18,E,1 297 | V,19,E,1 298 | V,20,E,1 299 | V,34,L,1 300 | V,35,L,1 301 | V,36,L,1 302 | V,37,L,1 303 | V,38,L,1 304 | V,50,E,1 305 | W,14,N,2 306 | W,15,N,2 307 | W,16,N,2 308 | W,17,N,2 309 | W,18,N,2 310 | W,30,D,3 311 | W,31,D,3 312 | W,32,D,3 313 | W,33,D,3 314 | W,37,D,3 315 | W,38,D,3 316 | W,39,D,3 317 | W,43,N,1 318 | W,53,N,1 319 | W,54,N,1 320 | X,0,D,1 321 | X,1,D,1 322 | X,2,D,1 323 | X,3,D,1 324 | X,14,L,2 325 | X,15,L,2 326 | X,16,L,2 327 | X,17,L,2 328 | X,25,L,2 329 | X,26,L,2 330 | X,41,L,1 331 | X,42,L,1 332 | X,43,L,1 333 | X,44,L,1 334 | X,48,D,3 335 | X,49,D,3 336 | X,50,D,3 337 | X,51,D,3 338 | X,52,D,3 339 | Y,10,D,2 340 | Y,11,D,2 341 | Y,12,D,2 342 | Y,13,D,2 343 | Y,18,N,1 344 | Y,19,N,1 345 | Y,20,N,1 346 | Y,21,N,1 347 | Y,22,N,1 348 | Y,30,E,3 349 | Y,31,E,3 350 | Y,32,E,3 351 | Y,33,E,3 352 | Y,34,E,3 353 | Z,5,N,3 354 | Z,9,E,3 355 | Z,10,E,3 356 | Z,11,E,3 357 | Z,12,E,3 358 | Z,13,E,3 359 | Z,17,N,3 360 | Z,18,N,3 361 | Z,26,E,2 362 | Z,32,E,3 363 | Z,33,E,3 364 | Z,34,E,3 365 | AA,18,D,1 366 | AA,31,E,1 367 | AA,32,E,1 368 | AA,33,E,1 369 | AA,41,E,2 370 | AA,42,E,2 371 | AA,43,E,2 372 | AB,5,E,3 373 | AB,6,E,3 374 | AB,7,E,3 375 | AB,8,E,3 376 | AB,13,L,3 377 | AB,14,L,3 378 | AB,15,L,3 379 | AB,16,L,3 380 | AB,22,D,2 381 | AB,23,D,2 382 | AB,29,N,3 383 | AB,30,N,3 384 | AB,31,N,3 385 | AB,32,N,3 386 | AB,39,N,1 387 | AC,2,E,1 388 | AC,3,E,1 389 | AC,4,E,1 390 | AC,5,E,1 391 | AC,9,D,2 392 | AC,10,D,2 393 | AC,11,D,2 394 | AC,12,D,2 395 | AC,13,D,2 396 | AC,19,D,3 397 | AC,20,D,3 398 | AC,27,D,3 399 | AC,28,D,3 400 | AC,29,D,3 401 | AC,30,D,3 402 | AC,46,L,2 403 | AC,47,L,2 404 | AC,51,L,3 405 | AC,52,L,3 406 | AC,53,L,3 407 | AD,2,D,3 408 | AD,3,D,3 409 | AD,4,D,3 410 | AD,5,D,3 411 | AD,20,L,3 412 | AE,0,L,2 413 | AE,9,E,1 414 | AE,10,E,1 415 | AE,26,D,1 416 | AE,27,D,1 417 | AE,28,D,1 418 | AE,29,D,1 419 | AF,3,L,2 420 | AF,4,L,2 421 | AF,5,L,2 422 | AF,11,N,3 423 | AF,12,N,3 424 | AF,13,N,3 425 | AF,14,N,3 426 | AF,15,N,3 427 | AF,19,E,3 428 | AF,20,E,3 429 | AF,21,E,3 430 | AF,22,E,3 431 | AF,23,E,3 432 | AF,38,E,1 433 | AF,39,E,1 434 | AF,40,E,1 435 | AF,49,E,1 436 | AF,50,E,1 437 | 438 | SECTION_SHIFT_OFF_REQUESTS 439 | # EmployeeID, Day, ShiftID, Weight 440 | A,5,E,3 441 | A,6,E,3 442 | A,7,E,3 443 | A,8,E,3 444 | A,14,D,2 445 | A,15,D,2 446 | A,16,D,2 447 | A,17,D,2 448 | A,18,D,2 449 | B,7,E,3 450 | B,8,E,3 451 | C,3,D,3 452 | C,4,D,3 453 | C,5,D,3 454 | D,2,D,2 455 | D,3,D,2 456 | D,14,L,2 457 | D,15,L,2 458 | D,16,L,2 459 | D,17,L,2 460 | D,18,L,2 461 | F,26,N,3 462 | F,27,N,3 463 | F,47,E,2 464 | F,48,E,2 465 | F,49,E,2 466 | F,50,E,2 467 | F,51,E,2 468 | G,7,L,3 469 | G,8,L,3 470 | G,9,L,3 471 | G,10,L,3 472 | H,46,E,1 473 | H,47,E,1 474 | H,48,E,1 475 | I,33,D,1 476 | I,34,D,1 477 | I,35,D,1 478 | J,28,D,2 479 | J,29,D,2 480 | J,30,D,2 481 | J,36,D,2 482 | J,37,D,2 483 | J,38,D,2 484 | K,36,D,1 485 | K,37,D,1 486 | L,1,N,1 487 | L,2,N,1 488 | M,8,E,3 489 | M,29,L,1 490 | M,35,E,1 491 | M,36,E,1 492 | M,37,E,1 493 | N,8,N,1 494 | N,9,N,1 495 | N,42,E,2 496 | N,43,E,2 497 | N,53,L,2 498 | N,54,L,2 499 | N,55,L,2 500 | O,13,E,3 501 | O,20,E,2 502 | O,21,E,2 503 | O,22,E,2 504 | O,23,E,2 505 | P,33,N,2 506 | P,34,N,2 507 | P,35,N,2 508 | P,36,N,2 509 | P,37,N,2 510 | Q,11,N,1 511 | Q,51,L,1 512 | Q,52,L,1 513 | Q,53,L,1 514 | Q,54,L,1 515 | Q,55,L,1 516 | S,7,D,1 517 | S,8,D,1 518 | S,9,D,1 519 | S,43,D,2 520 | S,44,D,2 521 | S,45,D,2 522 | S,46,D,2 523 | S,47,D,2 524 | T,22,E,2 525 | T,23,E,2 526 | T,24,E,2 527 | T,25,E,2 528 | T,36,N,2 529 | T,40,L,2 530 | T,41,L,2 531 | T,42,L,2 532 | T,43,L,2 533 | T,44,L,2 534 | V,1,L,2 535 | V,2,L,2 536 | V,3,L,2 537 | V,4,L,2 538 | W,24,E,3 539 | W,25,E,3 540 | W,26,E,3 541 | X,30,D,2 542 | Z,44,N,1 543 | AA,0,D,2 544 | AA,1,D,2 545 | AA,2,D,2 546 | AA,6,N,1 547 | AA,7,N,1 548 | AA,50,E,2 549 | AA,51,E,2 550 | AB,46,E,1 551 | AB,47,E,1 552 | AB,48,E,1 553 | AC,38,L,2 554 | AC,39,L,2 555 | AC,40,L,2 556 | AC,41,L,2 557 | AC,42,L,2 558 | AD,33,E,2 559 | AD,34,E,2 560 | AD,35,E,2 561 | AD,36,E,2 562 | AD,49,D,2 563 | AD,50,D,2 564 | AD,51,D,2 565 | AE,37,E,2 566 | AE,38,E,2 567 | AE,39,E,2 568 | AE,40,E,2 569 | 570 | SECTION_COVER 571 | # Day, ShiftID, Requirement, Weight for under, Weight for over 572 | 0,E,5,100,1 573 | 0,D,5,100,1 574 | 0,L,6,100,1 575 | 0,N,4,100,1 576 | 1,E,5,100,1 577 | 1,D,6,100,1 578 | 1,L,5,100,1 579 | 1,N,4,100,1 580 | 2,E,6,100,1 581 | 2,D,6,100,1 582 | 2,L,5,100,1 583 | 2,N,3,100,1 584 | 3,E,5,100,1 585 | 3,D,6,100,1 586 | 3,L,3,100,1 587 | 3,N,4,100,1 588 | 4,E,6,100,1 589 | 4,D,4,100,1 590 | 4,L,3,100,1 591 | 4,N,3,100,1 592 | 5,E,6,100,1 593 | 5,D,5,100,1 594 | 5,L,5,100,1 595 | 5,N,4,100,1 596 | 6,E,5,100,1 597 | 6,D,4,100,1 598 | 6,L,7,100,1 599 | 6,N,4,100,1 600 | 7,E,5,100,1 601 | 7,D,6,100,1 602 | 7,L,7,100,1 603 | 7,N,5,100,1 604 | 8,E,4,100,1 605 | 8,D,6,100,1 606 | 8,L,4,100,1 607 | 8,N,3,100,1 608 | 9,E,3,100,1 609 | 9,D,8,100,1 610 | 9,L,5,100,1 611 | 9,N,2,100,1 612 | 10,E,5,100,1 613 | 10,D,6,100,1 614 | 10,L,3,100,1 615 | 10,N,2,100,1 616 | 11,E,5,100,1 617 | 11,D,6,100,1 618 | 11,L,3,100,1 619 | 11,N,4,100,1 620 | 12,E,5,100,1 621 | 12,D,6,100,1 622 | 12,L,6,100,1 623 | 12,N,4,100,1 624 | 13,E,6,100,1 625 | 13,D,5,100,1 626 | 13,L,7,100,1 627 | 13,N,3,100,1 628 | 14,E,3,100,1 629 | 14,D,7,100,1 630 | 14,L,5,100,1 631 | 14,N,5,100,1 632 | 15,E,4,100,1 633 | 15,D,6,100,1 634 | 15,L,5,100,1 635 | 15,N,4,100,1 636 | 16,E,5,100,1 637 | 16,D,6,100,1 638 | 16,L,5,100,1 639 | 16,N,3,100,1 640 | 17,E,4,100,1 641 | 17,D,7,100,1 642 | 17,L,6,100,1 643 | 17,N,3,100,1 644 | 18,E,6,100,1 645 | 18,D,6,100,1 646 | 18,L,5,100,1 647 | 18,N,5,100,1 648 | 19,E,5,100,1 649 | 19,D,6,100,1 650 | 19,L,5,100,1 651 | 19,N,6,100,1 652 | 20,E,3,100,1 653 | 20,D,4,100,1 654 | 20,L,5,100,1 655 | 20,N,3,100,1 656 | 21,E,5,100,1 657 | 21,D,5,100,1 658 | 21,L,4,100,1 659 | 21,N,4,100,1 660 | 22,E,6,100,1 661 | 22,D,6,100,1 662 | 22,L,5,100,1 663 | 22,N,5,100,1 664 | 23,E,6,100,1 665 | 23,D,6,100,1 666 | 23,L,6,100,1 667 | 23,N,4,100,1 668 | 24,E,5,100,1 669 | 24,D,8,100,1 670 | 24,L,3,100,1 671 | 24,N,3,100,1 672 | 25,E,5,100,1 673 | 25,D,6,100,1 674 | 25,L,6,100,1 675 | 25,N,4,100,1 676 | 26,E,6,100,1 677 | 26,D,6,100,1 678 | 26,L,5,100,1 679 | 26,N,4,100,1 680 | 27,E,5,100,1 681 | 27,D,6,100,1 682 | 27,L,5,100,1 683 | 27,N,5,100,1 684 | 28,E,5,100,1 685 | 28,D,8,100,1 686 | 28,L,7,100,1 687 | 28,N,4,100,1 688 | 29,E,6,100,1 689 | 29,D,4,100,1 690 | 29,L,6,100,1 691 | 29,N,5,100,1 692 | 30,E,4,100,1 693 | 30,D,6,100,1 694 | 30,L,4,100,1 695 | 30,N,4,100,1 696 | 31,E,5,100,1 697 | 31,D,6,100,1 698 | 31,L,5,100,1 699 | 31,N,4,100,1 700 | 32,E,5,100,1 701 | 32,D,6,100,1 702 | 32,L,4,100,1 703 | 32,N,3,100,1 704 | 33,E,5,100,1 705 | 33,D,7,100,1 706 | 33,L,6,100,1 707 | 33,N,3,100,1 708 | 34,E,5,100,1 709 | 34,D,5,100,1 710 | 34,L,5,100,1 711 | 34,N,5,100,1 712 | 35,E,6,100,1 713 | 35,D,5,100,1 714 | 35,L,6,100,1 715 | 35,N,2,100,1 716 | 36,E,6,100,1 717 | 36,D,7,100,1 718 | 36,L,6,100,1 719 | 36,N,5,100,1 720 | 37,E,5,100,1 721 | 37,D,5,100,1 722 | 37,L,5,100,1 723 | 37,N,4,100,1 724 | 38,E,4,100,1 725 | 38,D,6,100,1 726 | 38,L,3,100,1 727 | 38,N,4,100,1 728 | 39,E,5,100,1 729 | 39,D,8,100,1 730 | 39,L,3,100,1 731 | 39,N,4,100,1 732 | 40,E,6,100,1 733 | 40,D,6,100,1 734 | 40,L,5,100,1 735 | 40,N,4,100,1 736 | 41,E,7,100,1 737 | 41,D,8,100,1 738 | 41,L,5,100,1 739 | 41,N,4,100,1 740 | 42,E,6,100,1 741 | 42,D,6,100,1 742 | 42,L,5,100,1 743 | 42,N,3,100,1 744 | 43,E,4,100,1 745 | 43,D,5,100,1 746 | 43,L,5,100,1 747 | 43,N,4,100,1 748 | 44,E,5,100,1 749 | 44,D,5,100,1 750 | 44,L,5,100,1 751 | 44,N,4,100,1 752 | 45,E,7,100,1 753 | 45,D,5,100,1 754 | 45,L,6,100,1 755 | 45,N,4,100,1 756 | 46,E,5,100,1 757 | 46,D,5,100,1 758 | 46,L,4,100,1 759 | 46,N,4,100,1 760 | 47,E,4,100,1 761 | 47,D,4,100,1 762 | 47,L,5,100,1 763 | 47,N,4,100,1 764 | 48,E,2,100,1 765 | 48,D,4,100,1 766 | 48,L,4,100,1 767 | 48,N,3,100,1 768 | 49,E,6,100,1 769 | 49,D,5,100,1 770 | 49,L,6,100,1 771 | 49,N,5,100,1 772 | 50,E,3,100,1 773 | 50,D,6,100,1 774 | 50,L,4,100,1 775 | 50,N,4,100,1 776 | 51,E,4,100,1 777 | 51,D,6,100,1 778 | 51,L,4,100,1 779 | 51,N,6,100,1 780 | 52,E,3,100,1 781 | 52,D,4,100,1 782 | 52,L,4,100,1 783 | 52,N,3,100,1 784 | 53,E,4,100,1 785 | 53,D,6,100,1 786 | 53,L,5,100,1 787 | 53,N,3,100,1 788 | 54,E,3,100,1 789 | 54,D,6,100,1 790 | 54,L,5,100,1 791 | 54,N,6,100,1 792 | 55,E,4,100,1 793 | 55,D,5,100,1 794 | 55,L,4,100,1 795 | 55,N,2,100,1 796 | -------------------------------------------------------------------------------- /Benchmarks/Instance18.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 84 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | D,480,E 11 | L,480,E|D 12 | 13 | SECTION_STAFF 14 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 15 | A,E=0|D=84|L=43,25920,24960,5,2,2,6 16 | B,E=84|D=0|L=43,25920,24960,5,2,2,6 17 | C,E=84|D=84|L=43,25920,24960,5,2,2,6 18 | D,E=84|D=84|L=0,25920,24960,5,2,2,6 19 | E,E=0|D=84|L=43,25920,24960,5,2,2,6 20 | F,E=0|D=84|L=43,25920,24960,5,2,2,6 21 | G,E=84|D=84|L=43,25920,24960,5,2,2,6 22 | H,E=84|D=84|L=43,25920,24960,5,2,2,6 23 | I,E=84|D=84|L=43,25920,24960,5,2,2,6 24 | J,E=84|D=84|L=43,25920,24960,5,2,2,6 25 | K,E=84|D=84|L=43,25920,24960,5,2,2,6 26 | L,E=84|D=84|L=43,25920,24960,5,2,2,6 27 | M,E=0|D=84|L=43,25920,24960,5,2,2,6 28 | N,E=84|D=84|L=43,25920,24960,5,2,2,6 29 | O,E=84|D=84|L=0,25920,24960,5,2,2,6 30 | P,E=84|D=84|L=43,25920,24960,5,2,2,6 31 | Q,E=84|D=0|L=43,25920,24960,5,2,2,6 32 | R,E=84|D=84|L=43,25920,24960,5,2,2,6 33 | S,E=0|D=84|L=0,24300,23340,4,2,2,6 34 | T,E=84|D=84|L=0,24300,23340,4,2,2,6 35 | U,E=84|D=84|L=40,24300,23340,4,2,2,6 36 | V,E=0|D=84|L=40,24300,23340,4,2,2,6 37 | 38 | SECTION_DAYS_OFF 39 | # EmployeeID, DayIndexes (start at zero) 40 | A,31,32,33,34,35,36,37,38 41 | B,23,33,38,39,40,41,56,61 42 | C,55,56,57,58,59,60,61,76 43 | D,15,16,17,18,19,20,21,22 44 | E,23,24,25,26,63,67,68,78 45 | F,17,18,37,38,39,40,60,81 46 | G,12,13,14,15,16,17,18,31 47 | H,2,3,4,11,30,49,50,51 48 | I,30,31,32,33,34,35,36,37 49 | J,4,5,6,7,8,9,10,14 50 | K,37,38,39,40,41,75,76,77 51 | L,20,21,22,42,43,44,45,46 52 | M,19,20,38,39,40,52,53,54 53 | N,8,52,53,54,55,69,70,71 54 | O,5,25,49,50,51,52,53,54 55 | P,32,57,58,59,70,71,72,73 56 | Q,53,54,55,56,57,66,78,79 57 | R,26,27,28,29,30,31,32,33 58 | S,31,32,33,34,35,36,37,38 59 | T,32,39,40,41,52,53,54,55 60 | U,9,10,11,12,13,48,49,50 61 | V,16,46,47,48,49,50,51,52 62 | 63 | SECTION_SHIFT_ON_REQUESTS 64 | # EmployeeID, Day, ShiftID, Weight 65 | A,17,L,2 66 | A,18,L,2 67 | A,19,L,2 68 | A,20,L,2 69 | A,21,L,2 70 | A,49,L,2 71 | A,50,L,2 72 | A,51,L,2 73 | A,52,L,2 74 | A,58,D,1 75 | A,59,D,1 76 | A,60,D,1 77 | A,61,D,1 78 | A,62,D,1 79 | A,66,L,3 80 | A,67,L,3 81 | A,68,L,3 82 | B,8,L,2 83 | B,9,L,2 84 | B,10,L,2 85 | B,11,L,2 86 | B,12,L,2 87 | B,16,E,2 88 | B,17,E,2 89 | B,18,E,2 90 | B,42,L,2 91 | C,28,E,3 92 | C,29,E,3 93 | C,34,D,1 94 | C,35,D,1 95 | C,40,D,2 96 | C,67,E,3 97 | C,72,L,2 98 | C,80,L,1 99 | C,81,L,1 100 | C,82,L,1 101 | D,1,D,2 102 | D,9,E,2 103 | D,10,E,2 104 | D,11,E,2 105 | D,12,E,2 106 | D,13,E,2 107 | D,23,E,3 108 | D,24,E,3 109 | D,25,E,3 110 | D,33,E,3 111 | D,34,E,3 112 | D,39,D,3 113 | D,40,D,3 114 | D,41,D,3 115 | D,58,E,2 116 | D,59,E,2 117 | D,60,E,2 118 | D,61,E,2 119 | D,62,E,2 120 | D,70,E,1 121 | D,71,E,1 122 | E,3,D,3 123 | E,11,D,1 124 | E,12,D,1 125 | E,13,D,1 126 | E,34,L,2 127 | E,35,L,2 128 | E,36,L,2 129 | E,41,L,1 130 | E,58,L,2 131 | E,59,L,2 132 | E,60,L,2 133 | E,71,D,3 134 | E,72,D,3 135 | E,73,D,3 136 | E,80,L,2 137 | E,81,L,2 138 | E,82,L,2 139 | F,21,L,3 140 | F,22,L,3 141 | F,23,L,3 142 | F,24,L,3 143 | F,29,D,2 144 | F,53,L,3 145 | F,54,L,3 146 | F,55,L,3 147 | F,56,L,3 148 | F,74,D,1 149 | F,75,D,1 150 | F,76,D,1 151 | G,19,E,1 152 | G,20,E,1 153 | G,21,E,1 154 | G,22,E,1 155 | G,26,E,3 156 | G,38,L,1 157 | G,39,L,1 158 | G,45,L,2 159 | G,46,L,2 160 | G,47,L,2 161 | G,48,L,2 162 | G,49,L,2 163 | G,63,E,2 164 | H,23,L,3 165 | H,24,L,3 166 | H,44,E,1 167 | H,45,E,1 168 | H,46,E,1 169 | H,52,L,3 170 | H,68,D,3 171 | H,69,D,3 172 | H,76,D,3 173 | I,50,E,3 174 | I,58,E,1 175 | I,59,E,1 176 | I,60,E,1 177 | I,61,E,1 178 | I,62,E,1 179 | I,71,L,3 180 | I,72,L,3 181 | I,76,D,1 182 | I,77,D,1 183 | J,0,D,1 184 | J,12,D,2 185 | J,13,D,2 186 | J,31,E,2 187 | J,32,E,2 188 | J,33,E,2 189 | J,34,E,2 190 | J,54,L,2 191 | J,59,L,3 192 | J,60,L,3 193 | J,61,L,3 194 | J,62,L,3 195 | J,73,E,3 196 | J,74,E,3 197 | J,75,E,3 198 | J,76,E,3 199 | J,77,E,3 200 | K,9,L,2 201 | K,10,L,2 202 | K,11,L,2 203 | K,12,L,2 204 | K,20,D,1 205 | K,21,D,1 206 | K,22,D,1 207 | K,23,D,1 208 | K,43,L,2 209 | K,44,L,2 210 | K,55,E,1 211 | K,56,E,1 212 | K,57,E,1 213 | K,58,E,1 214 | K,63,D,3 215 | K,64,D,3 216 | K,69,D,1 217 | K,70,D,1 218 | K,83,L,2 219 | L,1,L,3 220 | L,2,L,3 221 | L,3,L,3 222 | L,4,L,3 223 | L,5,L,3 224 | L,12,E,1 225 | L,13,E,1 226 | L,24,E,3 227 | L,25,E,3 228 | L,26,E,3 229 | L,27,E,3 230 | L,28,E,3 231 | L,47,L,1 232 | L,61,D,3 233 | L,62,D,3 234 | L,63,D,3 235 | L,64,D,3 236 | L,65,D,3 237 | M,32,D,1 238 | M,33,D,1 239 | M,34,D,1 240 | M,56,D,2 241 | M,57,D,2 242 | M,58,D,2 243 | M,59,D,2 244 | M,60,D,2 245 | M,74,L,1 246 | M,75,L,1 247 | M,76,L,1 248 | M,77,L,1 249 | M,78,L,1 250 | N,17,E,1 251 | N,18,E,1 252 | N,19,E,1 253 | N,25,D,1 254 | N,26,D,1 255 | N,27,D,1 256 | N,62,L,1 257 | N,63,L,1 258 | N,64,L,1 259 | N,74,D,2 260 | N,75,D,2 261 | N,76,D,2 262 | N,77,D,2 263 | O,15,D,1 264 | O,16,D,1 265 | O,17,D,1 266 | O,18,D,1 267 | O,19,D,1 268 | O,30,E,3 269 | O,31,E,3 270 | O,40,E,1 271 | O,41,E,1 272 | O,42,E,1 273 | O,43,E,1 274 | O,58,D,3 275 | O,59,D,3 276 | O,60,D,3 277 | O,68,E,2 278 | O,69,E,2 279 | O,76,E,2 280 | O,77,E,2 281 | O,78,E,2 282 | O,79,E,2 283 | O,80,E,2 284 | P,11,D,2 285 | P,12,D,2 286 | P,17,D,2 287 | P,18,D,2 288 | P,19,D,2 289 | P,45,D,1 290 | P,46,D,1 291 | P,47,D,1 292 | P,54,D,1 293 | P,55,D,1 294 | P,62,L,3 295 | P,63,L,3 296 | P,78,E,3 297 | P,79,E,3 298 | P,80,E,3 299 | P,81,E,3 300 | Q,11,L,2 301 | Q,12,L,2 302 | Q,19,E,1 303 | Q,20,E,1 304 | Q,21,E,1 305 | Q,22,E,1 306 | Q,30,E,1 307 | Q,31,E,1 308 | Q,32,E,1 309 | Q,38,L,2 310 | Q,39,L,2 311 | Q,40,L,2 312 | Q,41,L,2 313 | Q,59,L,3 314 | Q,60,L,3 315 | Q,68,E,3 316 | Q,69,E,3 317 | Q,70,E,3 318 | Q,71,E,3 319 | R,3,L,1 320 | R,4,L,1 321 | R,5,L,1 322 | R,6,L,1 323 | R,7,L,1 324 | R,12,D,1 325 | R,46,D,1 326 | R,47,D,1 327 | R,48,D,1 328 | R,49,D,1 329 | R,58,E,2 330 | R,59,E,2 331 | R,60,E,2 332 | R,61,E,2 333 | R,74,E,1 334 | R,75,E,1 335 | R,76,E,1 336 | R,77,E,1 337 | R,78,E,1 338 | S,15,D,2 339 | S,16,D,2 340 | S,40,D,1 341 | S,45,D,2 342 | S,64,D,3 343 | S,65,D,3 344 | S,66,D,3 345 | S,67,D,3 346 | S,74,D,3 347 | S,75,D,3 348 | S,76,D,3 349 | S,77,D,3 350 | S,78,D,3 351 | T,5,D,3 352 | T,11,E,1 353 | T,15,D,3 354 | T,16,D,3 355 | T,17,D,3 356 | T,27,D,1 357 | T,75,D,2 358 | T,76,D,2 359 | T,77,D,2 360 | U,38,D,3 361 | U,44,E,2 362 | U,45,E,2 363 | U,46,E,2 364 | U,47,E,2 365 | U,58,E,1 366 | U,63,D,2 367 | U,68,D,3 368 | U,69,D,3 369 | U,70,D,3 370 | V,7,L,2 371 | V,8,L,2 372 | V,19,D,3 373 | V,20,D,3 374 | V,21,D,3 375 | V,27,D,1 376 | V,28,D,1 377 | V,29,D,1 378 | V,30,D,1 379 | V,31,D,1 380 | V,38,L,1 381 | V,42,L,3 382 | V,57,D,2 383 | V,58,D,2 384 | V,59,D,2 385 | V,79,D,1 386 | V,80,D,1 387 | 388 | SECTION_SHIFT_OFF_REQUESTS 389 | # EmployeeID, Day, ShiftID, Weight 390 | A,6,D,1 391 | A,82,D,2 392 | B,48,L,1 393 | B,49,L,1 394 | B,50,L,1 395 | B,51,L,1 396 | B,52,L,1 397 | B,64,E,3 398 | B,65,E,3 399 | B,76,E,1 400 | B,77,E,1 401 | B,78,E,1 402 | C,1,L,3 403 | C,2,L,3 404 | C,3,L,3 405 | C,9,E,1 406 | C,10,E,1 407 | C,11,E,1 408 | C,12,E,1 409 | C,17,D,3 410 | C,18,D,3 411 | C,19,D,3 412 | C,20,D,3 413 | C,52,E,3 414 | C,53,E,3 415 | F,12,L,1 416 | F,13,L,1 417 | G,0,L,3 418 | G,1,L,3 419 | G,2,L,3 420 | G,3,L,3 421 | G,53,E,1 422 | G,54,E,1 423 | G,55,E,1 424 | G,56,E,1 425 | G,57,E,1 426 | G,70,D,2 427 | G,71,D,2 428 | H,31,E,1 429 | I,41,E,1 430 | I,42,E,1 431 | I,43,E,1 432 | J,21,L,2 433 | J,22,L,2 434 | J,23,L,2 435 | L,52,D,3 436 | L,53,D,3 437 | L,79,D,2 438 | L,80,D,2 439 | M,2,L,3 440 | M,3,L,3 441 | M,4,L,3 442 | M,5,L,3 443 | M,26,L,2 444 | M,27,L,2 445 | M,28,L,2 446 | M,64,L,2 447 | M,65,L,2 448 | M,66,L,2 449 | M,67,L,2 450 | M,68,L,2 451 | M,83,D,2 452 | N,35,L,1 453 | N,42,D,1 454 | N,57,D,2 455 | P,3,E,1 456 | P,4,E,1 457 | P,26,D,2 458 | P,27,D,2 459 | P,28,D,2 460 | P,29,D,2 461 | T,0,E,1 462 | T,1,E,1 463 | T,35,E,2 464 | T,36,E,2 465 | T,68,E,2 466 | T,69,E,2 467 | T,70,E,2 468 | T,71,E,2 469 | U,3,D,2 470 | U,4,D,2 471 | U,5,D,2 472 | U,6,D,2 473 | U,7,D,2 474 | U,31,L,1 475 | U,75,D,2 476 | U,76,D,2 477 | U,77,D,2 478 | U,78,D,2 479 | U,79,D,2 480 | V,73,L,2 481 | V,74,L,2 482 | 483 | SECTION_COVER 484 | # Day, ShiftID, Requirement, Weight for under, Weight for over 485 | 0,E,5,100,1 486 | 0,D,4,100,1 487 | 0,L,6,100,1 488 | 1,E,5,100,1 489 | 1,D,3,100,1 490 | 1,L,7,100,1 491 | 2,E,5,100,1 492 | 2,D,3,100,1 493 | 2,L,5,100,1 494 | 3,E,6,100,1 495 | 3,D,3,100,1 496 | 3,L,7,100,1 497 | 4,E,7,100,1 498 | 4,D,3,100,1 499 | 4,L,5,100,1 500 | 5,E,5,100,1 501 | 5,D,3,100,1 502 | 5,L,3,100,1 503 | 6,E,5,100,1 504 | 6,D,3,100,1 505 | 6,L,7,100,1 506 | 7,E,5,100,1 507 | 7,D,4,100,1 508 | 7,L,5,100,1 509 | 8,E,5,100,1 510 | 8,D,3,100,1 511 | 8,L,4,100,1 512 | 9,E,6,100,1 513 | 9,D,3,100,1 514 | 9,L,6,100,1 515 | 10,E,7,100,1 516 | 10,D,4,100,1 517 | 10,L,5,100,1 518 | 11,E,7,100,1 519 | 11,D,4,100,1 520 | 11,L,6,100,1 521 | 12,E,4,100,1 522 | 12,D,2,100,1 523 | 12,L,5,100,1 524 | 13,E,3,100,1 525 | 13,D,3,100,1 526 | 13,L,6,100,1 527 | 14,E,5,100,1 528 | 14,D,3,100,1 529 | 14,L,5,100,1 530 | 15,E,5,100,1 531 | 15,D,2,100,1 532 | 15,L,5,100,1 533 | 16,E,5,100,1 534 | 16,D,3,100,1 535 | 16,L,6,100,1 536 | 17,E,4,100,1 537 | 17,D,2,100,1 538 | 17,L,4,100,1 539 | 18,E,3,100,1 540 | 18,D,2,100,1 541 | 18,L,7,100,1 542 | 19,E,5,100,1 543 | 19,D,4,100,1 544 | 19,L,5,100,1 545 | 20,E,3,100,1 546 | 20,D,3,100,1 547 | 20,L,6,100,1 548 | 21,E,5,100,1 549 | 21,D,2,100,1 550 | 21,L,5,100,1 551 | 22,E,3,100,1 552 | 22,D,3,100,1 553 | 22,L,7,100,1 554 | 23,E,4,100,1 555 | 23,D,3,100,1 556 | 23,L,6,100,1 557 | 24,E,7,100,1 558 | 24,D,5,100,1 559 | 24,L,5,100,1 560 | 25,E,5,100,1 561 | 25,D,3,100,1 562 | 25,L,6,100,1 563 | 26,E,4,100,1 564 | 26,D,5,100,1 565 | 26,L,5,100,1 566 | 27,E,3,100,1 567 | 27,D,3,100,1 568 | 27,L,4,100,1 569 | 28,E,6,100,1 570 | 28,D,4,100,1 571 | 28,L,7,100,1 572 | 29,E,5,100,1 573 | 29,D,3,100,1 574 | 29,L,5,100,1 575 | 30,E,5,100,1 576 | 30,D,3,100,1 577 | 30,L,5,100,1 578 | 31,E,6,100,1 579 | 31,D,4,100,1 580 | 31,L,5,100,1 581 | 32,E,5,100,1 582 | 32,D,3,100,1 583 | 32,L,4,100,1 584 | 33,E,5,100,1 585 | 33,D,3,100,1 586 | 33,L,5,100,1 587 | 34,E,7,100,1 588 | 34,D,3,100,1 589 | 34,L,5,100,1 590 | 35,E,6,100,1 591 | 35,D,4,100,1 592 | 35,L,5,100,1 593 | 36,E,6,100,1 594 | 36,D,3,100,1 595 | 36,L,5,100,1 596 | 37,E,7,100,1 597 | 37,D,4,100,1 598 | 37,L,5,100,1 599 | 38,E,5,100,1 600 | 38,D,4,100,1 601 | 38,L,5,100,1 602 | 39,E,4,100,1 603 | 39,D,4,100,1 604 | 39,L,6,100,1 605 | 40,E,4,100,1 606 | 40,D,4,100,1 607 | 40,L,6,100,1 608 | 41,E,5,100,1 609 | 41,D,3,100,1 610 | 41,L,3,100,1 611 | 42,E,7,100,1 612 | 42,D,5,100,1 613 | 42,L,6,100,1 614 | 43,E,5,100,1 615 | 43,D,5,100,1 616 | 43,L,5,100,1 617 | 44,E,6,100,1 618 | 44,D,3,100,1 619 | 44,L,5,100,1 620 | 45,E,5,100,1 621 | 45,D,3,100,1 622 | 45,L,5,100,1 623 | 46,E,6,100,1 624 | 46,D,3,100,1 625 | 46,L,5,100,1 626 | 47,E,5,100,1 627 | 47,D,2,100,1 628 | 47,L,4,100,1 629 | 48,E,4,100,1 630 | 48,D,3,100,1 631 | 48,L,5,100,1 632 | 49,E,5,100,1 633 | 49,D,3,100,1 634 | 49,L,5,100,1 635 | 50,E,6,100,1 636 | 50,D,4,100,1 637 | 50,L,5,100,1 638 | 51,E,5,100,1 639 | 51,D,3,100,1 640 | 51,L,4,100,1 641 | 52,E,5,100,1 642 | 52,D,3,100,1 643 | 52,L,7,100,1 644 | 53,E,6,100,1 645 | 53,D,2,100,1 646 | 53,L,5,100,1 647 | 54,E,5,100,1 648 | 54,D,3,100,1 649 | 54,L,5,100,1 650 | 55,E,5,100,1 651 | 55,D,3,100,1 652 | 55,L,6,100,1 653 | 56,E,7,100,1 654 | 56,D,2,100,1 655 | 56,L,5,100,1 656 | 57,E,7,100,1 657 | 57,D,3,100,1 658 | 57,L,4,100,1 659 | 58,E,6,100,1 660 | 58,D,2,100,1 661 | 58,L,6,100,1 662 | 59,E,7,100,1 663 | 59,D,1,100,1 664 | 59,L,7,100,1 665 | 60,E,5,100,1 666 | 60,D,2,100,1 667 | 60,L,5,100,1 668 | 61,E,5,100,1 669 | 61,D,1,100,1 670 | 61,L,7,100,1 671 | 62,E,5,100,1 672 | 62,D,1,100,1 673 | 62,L,4,100,1 674 | 63,E,5,100,1 675 | 63,D,1,100,1 676 | 63,L,5,100,1 677 | 64,E,5,100,1 678 | 64,D,2,100,1 679 | 64,L,5,100,1 680 | 65,E,6,100,1 681 | 65,D,2,100,1 682 | 65,L,5,100,1 683 | 66,E,6,100,1 684 | 66,D,4,100,1 685 | 66,L,3,100,1 686 | 67,E,4,100,1 687 | 67,D,1,100,1 688 | 67,L,4,100,1 689 | 68,E,6,100,1 690 | 68,D,3,100,1 691 | 68,L,4,100,1 692 | 69,E,5,100,1 693 | 69,D,4,100,1 694 | 69,L,4,100,1 695 | 70,E,7,100,1 696 | 70,D,2,100,1 697 | 70,L,4,100,1 698 | 71,E,3,100,1 699 | 71,D,1,100,1 700 | 71,L,4,100,1 701 | 72,E,5,100,1 702 | 72,D,3,100,1 703 | 72,L,7,100,1 704 | 73,E,5,100,1 705 | 73,D,3,100,1 706 | 73,L,5,100,1 707 | 74,E,5,100,1 708 | 74,D,3,100,1 709 | 74,L,4,100,1 710 | 75,E,5,100,1 711 | 75,D,3,100,1 712 | 75,L,5,100,1 713 | 76,E,5,100,1 714 | 76,D,3,100,1 715 | 76,L,5,100,1 716 | 77,E,5,100,1 717 | 77,D,3,100,1 718 | 77,L,6,100,1 719 | 78,E,4,100,1 720 | 78,D,1,100,1 721 | 78,L,5,100,1 722 | 79,E,4,100,1 723 | 79,D,3,100,1 724 | 79,L,5,100,1 725 | 80,E,5,100,1 726 | 80,D,2,100,1 727 | 80,L,5,100,1 728 | 81,E,5,100,1 729 | 81,D,2,100,1 730 | 81,L,5,100,1 731 | 82,E,6,100,1 732 | 82,D,3,100,1 733 | 82,L,5,100,1 734 | 83,E,4,100,1 735 | 83,D,3,100,1 736 | 83,L,7,100,1 737 | -------------------------------------------------------------------------------- /Benchmarks/Instance2.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 14 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | L,480,E 11 | 12 | SECTION_STAFF 13 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 14 | A,E=14|L=14,4320,3360,5,2,2,1 15 | B,E=14|L=14,4320,3360,5,2,2,1 16 | C,E=14|L=14,4320,3360,5,2,2,1 17 | D,E=14|L=0,4320,3360,5,2,2,1 18 | E,E=0|L=14,4320,3360,5,2,2,1 19 | F,E=14|L=14,4320,3360,5,2,2,1 20 | G,E=14|L=14,4320,3360,5,2,2,1 21 | H,E=14|L=14,4320,3360,5,2,2,1 22 | I,E=14|L=14,4320,3360,5,2,2,1 23 | J,E=14|L=14,4320,3360,5,2,2,1 24 | K,E=0|L=14,2160,1200,5,1,1,1 25 | L,E=0|L=14,2160,1200,5,1,1,1 26 | M,E=14|L=14,2160,1200,5,1,1,1 27 | N,E=14|L=14,2160,1200,5,1,1,1 28 | 29 | SECTION_DAYS_OFF 30 | # EmployeeID, DayIndexes (start at zero) 31 | A,3 32 | B,1 33 | C,2 34 | D,12 35 | E,1 36 | F,13 37 | G,9 38 | H,3 39 | I,0 40 | J,8 41 | K,5 42 | L,2 43 | M,8 44 | N,6 45 | 46 | SECTION_SHIFT_ON_REQUESTS 47 | # EmployeeID, Day, ShiftID, Weight 48 | A,5,L,1 49 | A,6,L,1 50 | A,7,L,1 51 | A,8,L,1 52 | A,9,L,1 53 | B,7,E,1 54 | B,8,E,1 55 | B,9,E,1 56 | B,10,E,1 57 | C,8,E,1 58 | C,9,E,1 59 | C,10,E,1 60 | C,11,E,1 61 | D,1,E,1 62 | D,2,E,1 63 | D,3,E,1 64 | E,3,L,1 65 | E,4,L,1 66 | E,5,L,1 67 | E,6,L,1 68 | E,7,L,1 69 | E,12,L,2 70 | E,13,L,2 71 | F,3,L,3 72 | F,4,L,3 73 | F,5,L,3 74 | I,2,L,3 75 | I,3,L,3 76 | I,12,E,2 77 | J,11,L,3 78 | K,7,L,1 79 | K,8,L,1 80 | K,9,L,1 81 | L,3,L,1 82 | L,4,L,1 83 | L,10,L,3 84 | L,11,L,3 85 | L,12,L,3 86 | L,13,L,3 87 | M,3,L,1 88 | M,4,L,1 89 | M,5,L,1 90 | M,6,L,1 91 | M,7,L,1 92 | N,0,E,2 93 | N,1,E,2 94 | N,2,E,2 95 | N,8,E,3 96 | N,9,E,3 97 | N,10,E,3 98 | 99 | SECTION_SHIFT_OFF_REQUESTS 100 | # EmployeeID, Day, ShiftID, Weight 101 | G,3,E,2 102 | G,4,E,2 103 | G,5,E,2 104 | G,6,E,2 105 | G,7,E,2 106 | H,1,L,2 107 | J,1,E,1 108 | J,2,E,1 109 | J,3,E,1 110 | J,4,E,1 111 | J,5,E,1 112 | M,11,L,1 113 | 114 | SECTION_COVER 115 | # Day, ShiftID, Requirement, Weight for under, Weight for over 116 | 0,E,4,100,1 117 | 0,L,4,100,1 118 | 1,E,4,100,1 119 | 1,L,3,100,1 120 | 2,E,3,100,1 121 | 2,L,6,100,1 122 | 3,E,5,100,1 123 | 3,L,4,100,1 124 | 4,E,3,100,1 125 | 4,L,4,100,1 126 | 5,E,5,100,1 127 | 5,L,5,100,1 128 | 6,E,5,100,1 129 | 6,L,5,100,1 130 | 7,E,3,100,1 131 | 7,L,2,100,1 132 | 8,E,4,100,1 133 | 8,L,4,100,1 134 | 9,E,4,100,1 135 | 9,L,4,100,1 136 | 10,E,4,100,1 137 | 10,L,3,100,1 138 | 11,E,2,100,1 139 | 11,L,3,100,1 140 | 12,E,4,100,1 141 | 12,L,3,100,1 142 | 13,E,3,100,1 143 | 13,L,5,100,1 144 | -------------------------------------------------------------------------------- /Benchmarks/Instance3.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 14 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | D,480,E 11 | L,480,E|D 12 | 13 | SECTION_STAFF 14 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 15 | A,E=14|D=14|L=0,4320,3360,5,2,2,1 16 | B,E=14|D=14|L=5,4320,3360,5,2,2,1 17 | C,E=14|D=14|L=5,4320,3360,5,2,2,1 18 | D,E=14|D=0|L=5,4320,3360,5,2,2,1 19 | E,E=14|D=0|L=0,4320,3360,5,2,2,1 20 | F,E=14|D=14|L=0,4320,3360,5,2,2,1 21 | G,E=14|D=14|L=5,4320,3360,5,2,2,1 22 | H,E=14|D=14|L=5,4320,3360,5,2,2,1 23 | I,E=14|D=14|L=5,4320,3360,5,2,2,1 24 | J,E=14|D=14|L=0,4320,3360,5,2,2,1 25 | K,E=14|D=14|L=0,4320,3360,6,2,3,1 26 | L,E=0|D=14|L=5,4320,3360,6,2,3,1 27 | M,E=14|D=14|L=5,4320,3360,6,2,3,1 28 | N,E=0|D=14|L=5,4320,3360,6,2,3,1 29 | O,E=0|D=14|L=5,4320,3360,6,2,3,1 30 | P,E=14|D=14|L=2,2160,1200,5,1,2,1 31 | Q,E=0|D=14|L=2,2160,1200,5,1,2,1 32 | R,E=14|D=14|L=0,2160,1200,5,1,2,1 33 | S,E=14|D=0|L=2,2160,1200,5,1,2,1 34 | T,E=14|D=14|L=0,2160,1200,5,1,2,1 35 | 36 | SECTION_DAYS_OFF 37 | # EmployeeID, DayIndexes (start at zero) 38 | A,0 39 | B,6 40 | C,1 41 | D,0 42 | E,3 43 | F,0 44 | G,4 45 | H,12 46 | I,6 47 | J,2 48 | K,4 49 | L,4 50 | M,1 51 | N,8 52 | O,3 53 | P,2 54 | Q,6 55 | R,1 56 | S,8 57 | T,11 58 | 59 | SECTION_SHIFT_ON_REQUESTS 60 | # EmployeeID, Day, ShiftID, Weight 61 | B,0,D,1 62 | B,1,D,1 63 | B,2,D,1 64 | B,3,D,1 65 | B,4,D,1 66 | D,7,E,2 67 | D,8,E,2 68 | D,12,E,2 69 | D,13,E,2 70 | F,8,E,2 71 | F,12,D,3 72 | F,13,D,3 73 | G,10,L,2 74 | G,11,L,2 75 | H,2,E,1 76 | H,3,E,1 77 | I,0,D,2 78 | I,1,D,2 79 | I,2,D,2 80 | J,8,D,2 81 | J,9,D,2 82 | K,6,D,3 83 | K,7,D,3 84 | K,8,D,3 85 | K,9,D,3 86 | K,10,D,3 87 | L,8,L,2 88 | N,3,L,1 89 | N,4,L,1 90 | N,5,L,1 91 | N,6,L,1 92 | O,5,D,1 93 | O,6,D,1 94 | O,7,D,1 95 | S,1,E,3 96 | S,6,E,2 97 | S,7,E,2 98 | T,3,E,3 99 | T,4,E,3 100 | 101 | SECTION_SHIFT_OFF_REQUESTS 102 | # EmployeeID, Day, ShiftID, Weight 103 | A,9,E,2 104 | A,10,E,2 105 | A,11,E,2 106 | A,12,E,2 107 | A,13,E,2 108 | C,2,L,3 109 | H,7,E,2 110 | H,8,E,2 111 | H,9,E,2 112 | H,10,E,2 113 | M,3,E,2 114 | M,4,E,2 115 | M,5,E,2 116 | M,6,E,2 117 | M,7,E,2 118 | O,13,D,3 119 | P,5,L,3 120 | P,6,L,3 121 | P,7,L,3 122 | P,8,L,3 123 | Q,1,D,3 124 | Q,2,D,3 125 | Q,3,D,3 126 | Q,4,D,3 127 | Q,5,D,3 128 | 129 | SECTION_COVER 130 | # Day, ShiftID, Requirement, Weight for under, Weight for over 131 | 0,E,2,100,1 132 | 0,D,3,100,1 133 | 0,L,3,100,1 134 | 1,E,3,100,1 135 | 1,D,4,100,1 136 | 1,L,3,100,1 137 | 2,E,3,100,1 138 | 2,D,5,100,1 139 | 2,L,3,100,1 140 | 3,E,4,100,1 141 | 3,D,5,100,1 142 | 3,L,2,100,1 143 | 4,E,2,100,1 144 | 4,D,5,100,1 145 | 4,L,4,100,1 146 | 5,E,4,100,1 147 | 5,D,6,100,1 148 | 5,L,2,100,1 149 | 6,E,6,100,1 150 | 6,D,5,100,1 151 | 6,L,3,100,1 152 | 7,E,4,100,1 153 | 7,D,6,100,1 154 | 7,L,3,100,1 155 | 8,E,3,100,1 156 | 8,D,4,100,1 157 | 8,L,2,100,1 158 | 9,E,4,100,1 159 | 9,D,5,100,1 160 | 9,L,4,100,1 161 | 10,E,2,100,1 162 | 10,D,5,100,1 163 | 10,L,2,100,1 164 | 11,E,2,100,1 165 | 11,D,3,100,1 166 | 11,L,4,100,1 167 | 12,E,2,100,1 168 | 12,D,6,100,1 169 | 12,L,5,100,1 170 | 13,E,3,100,1 171 | 13,D,5,100,1 172 | 13,L,3,100,1 173 | -------------------------------------------------------------------------------- /Benchmarks/Instance4.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 28 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | L,480,E 11 | 12 | SECTION_STAFF 13 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 14 | A,E=28|L=28,8640,7560,5,2,2,2 15 | B,E=28|L=0,8640,7560,5,2,2,2 16 | C,E=28|L=28,8640,7560,5,2,2,2 17 | D,E=28|L=28,8640,7560,5,2,2,2 18 | E,E=28|L=28,8640,7560,5,2,2,2 19 | F,E=0|L=28,8640,7560,5,2,2,2 20 | G,E=28|L=28,8640,7560,5,2,2,2 21 | H,E=28|L=28,8640,7560,5,2,2,2 22 | I,E=28|L=28,8640,7560,5,2,2,2 23 | J,E=28|L=28,8640,7560,5,2,2,2 24 | 25 | SECTION_DAYS_OFF 26 | # EmployeeID, DayIndexes (start at zero) 27 | A,5,6 28 | B,11,16 29 | C,13,14 30 | D,2,12 31 | E,23,24 32 | F,8,17 33 | G,25,26 34 | H,10,11 35 | I,19,20 36 | J,21,24 37 | 38 | SECTION_SHIFT_ON_REQUESTS 39 | # EmployeeID, Day, ShiftID, Weight 40 | A,7,L,2 41 | A,8,L,2 42 | A,23,L,1 43 | B,7,E,2 44 | B,8,E,2 45 | B,9,E,2 46 | B,25,E,2 47 | C,1,E,3 48 | C,2,E,3 49 | C,6,E,3 50 | C,7,E,3 51 | C,8,E,3 52 | C,16,L,2 53 | C,17,L,2 54 | C,25,E,2 55 | C,26,E,2 56 | D,7,E,2 57 | D,8,E,2 58 | D,9,E,2 59 | D,10,E,2 60 | D,11,E,2 61 | D,19,E,1 62 | D,20,E,1 63 | D,21,E,1 64 | D,22,E,1 65 | D,23,E,1 66 | E,2,E,3 67 | E,3,E,3 68 | E,4,E,3 69 | E,5,E,3 70 | F,13,L,3 71 | F,14,L,3 72 | F,15,L,3 73 | G,6,L,3 74 | G,7,L,3 75 | G,8,L,3 76 | G,9,L,3 77 | G,10,L,3 78 | G,17,L,3 79 | G,18,L,3 80 | G,19,L,3 81 | H,13,E,3 82 | I,0,E,2 83 | I,1,E,2 84 | I,2,E,2 85 | I,10,L,2 86 | I,11,L,2 87 | I,12,L,2 88 | I,13,L,2 89 | I,21,E,2 90 | I,22,E,2 91 | I,23,E,2 92 | 93 | SECTION_SHIFT_OFF_REQUESTS 94 | # EmployeeID, Day, ShiftID, Weight 95 | B,17,E,3 96 | B,18,E,3 97 | E,16,L,3 98 | E,17,L,3 99 | E,18,L,3 100 | E,19,L,3 101 | E,20,L,3 102 | F,3,L,1 103 | F,4,L,1 104 | F,5,L,1 105 | F,6,L,1 106 | F,20,L,2 107 | F,21,L,2 108 | J,7,L,3 109 | J,8,L,3 110 | J,9,L,3 111 | J,13,L,1 112 | J,14,L,1 113 | J,15,L,1 114 | 115 | SECTION_COVER 116 | # Day, ShiftID, Requirement, Weight for under, Weight for over 117 | 0,E,2,100,1 118 | 0,L,3,100,1 119 | 1,E,3,100,1 120 | 1,L,3,100,1 121 | 2,E,4,100,1 122 | 2,L,4,100,1 123 | 3,E,2,100,1 124 | 3,L,3,100,1 125 | 4,E,2,100,1 126 | 4,L,3,100,1 127 | 5,E,2,100,1 128 | 5,L,3,100,1 129 | 6,E,2,100,1 130 | 6,L,3,100,1 131 | 7,E,3,100,1 132 | 7,L,3,100,1 133 | 8,E,4,100,1 134 | 8,L,3,100,1 135 | 9,E,3,100,1 136 | 9,L,2,100,1 137 | 10,E,2,100,1 138 | 10,L,3,100,1 139 | 11,E,4,100,1 140 | 11,L,3,100,1 141 | 12,E,2,100,1 142 | 12,L,3,100,1 143 | 13,E,5,100,1 144 | 13,L,3,100,1 145 | 14,E,3,100,1 146 | 14,L,4,100,1 147 | 15,E,5,100,1 148 | 15,L,4,100,1 149 | 16,E,4,100,1 150 | 16,L,4,100,1 151 | 17,E,3,100,1 152 | 17,L,2,100,1 153 | 18,E,2,100,1 154 | 18,L,3,100,1 155 | 19,E,6,100,1 156 | 19,L,4,100,1 157 | 20,E,2,100,1 158 | 20,L,2,100,1 159 | 21,E,4,100,1 160 | 21,L,4,100,1 161 | 22,E,3,100,1 162 | 22,L,2,100,1 163 | 23,E,5,100,1 164 | 23,L,3,100,1 165 | 24,E,4,100,1 166 | 24,L,4,100,1 167 | 25,E,4,100,1 168 | 25,L,6,100,1 169 | 26,E,4,100,1 170 | 26,L,4,100,1 171 | 27,E,4,100,1 172 | 27,L,1,100,1 173 | -------------------------------------------------------------------------------- /Benchmarks/Instance5.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 28 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | L,480,E 11 | 12 | SECTION_STAFF 13 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 14 | A,E=28|L=0,8640,7560,5,2,2,2 15 | B,E=28|L=0,8640,7560,5,2,2,2 16 | C,E=28|L=14,8640,7560,5,2,2,2 17 | D,E=28|L=14,8640,7560,5,2,2,2 18 | E,E=28|L=0,8640,7560,5,2,2,2 19 | F,E=28|L=14,8640,7560,5,2,2,2 20 | G,E=28|L=14,8640,7560,5,2,2,2 21 | H,E=28|L=14,8640,7560,5,2,2,2 22 | I,E=28|L=14,8640,7560,5,2,2,2 23 | J,E=28|L=14,8640,7560,5,2,2,2 24 | K,E=28|L=0,8640,7560,6,2,3,3 25 | L,E=28|L=0,8640,7560,6,2,3,3 26 | M,E=28|L=14,8640,7560,6,2,3,3 27 | N,E=28|L=0,8640,7560,6,2,3,3 28 | O,E=28|L=14,8640,7560,6,2,3,3 29 | P,E=28|L=14,8640,7560,6,2,3,3 30 | 31 | SECTION_DAYS_OFF 32 | # EmployeeID, DayIndexes (start at zero) 33 | A,17,22 34 | B,9,23 35 | C,5,6 36 | D,9,13 37 | E,22,23 38 | F,11,12 39 | G,8,19 40 | H,6,10 41 | I,18,19 42 | J,15,16 43 | K,5,6 44 | L,25,26 45 | M,1,7 46 | N,16,26 47 | O,2,3 48 | P,16,17 49 | 50 | SECTION_SHIFT_ON_REQUESTS 51 | # EmployeeID, Day, ShiftID, Weight 52 | A,2,E,3 53 | A,3,E,3 54 | A,4,E,3 55 | A,5,E,3 56 | C,2,E,3 57 | C,9,L,3 58 | C,10,L,3 59 | D,1,L,2 60 | D,2,L,2 61 | D,3,L,2 62 | D,4,L,2 63 | D,5,L,2 64 | D,10,E,3 65 | D,14,E,2 66 | D,15,E,2 67 | D,16,E,2 68 | D,20,L,2 69 | D,21,L,2 70 | D,22,L,2 71 | E,2,E,3 72 | E,3,E,3 73 | E,4,E,3 74 | E,5,E,3 75 | E,6,E,3 76 | E,12,E,3 77 | E,13,E,3 78 | E,14,E,3 79 | E,15,E,3 80 | E,16,E,3 81 | F,8,L,2 82 | G,20,L,1 83 | H,12,L,2 84 | H,19,L,3 85 | H,20,L,3 86 | H,21,L,3 87 | H,22,L,3 88 | H,23,L,3 89 | I,6,E,3 90 | I,12,L,1 91 | I,13,L,1 92 | I,14,L,1 93 | I,15,L,1 94 | J,1,E,1 95 | J,2,E,1 96 | J,3,E,1 97 | J,4,E,1 98 | J,10,L,2 99 | K,22,E,3 100 | K,23,E,3 101 | K,24,E,3 102 | K,25,E,3 103 | L,7,E,2 104 | L,8,E,2 105 | L,9,E,2 106 | L,10,E,2 107 | M,9,E,1 108 | M,10,E,1 109 | M,11,E,1 110 | M,12,E,1 111 | M,13,E,1 112 | M,21,E,1 113 | M,22,E,1 114 | M,23,E,1 115 | N,2,E,3 116 | N,3,E,3 117 | N,11,E,1 118 | N,18,E,2 119 | O,4,E,3 120 | O,15,L,3 121 | O,16,L,3 122 | O,17,L,3 123 | O,18,L,3 124 | P,2,E,1 125 | P,3,E,1 126 | P,4,E,1 127 | P,5,E,1 128 | P,12,E,3 129 | P,13,E,3 130 | P,14,E,3 131 | 132 | SECTION_SHIFT_OFF_REQUESTS 133 | # EmployeeID, Day, ShiftID, Weight 134 | A,10,E,2 135 | A,19,E,1 136 | B,3,E,2 137 | B,4,E,2 138 | B,5,E,2 139 | B,11,E,2 140 | B,12,E,2 141 | C,14,L,2 142 | C,25,L,1 143 | C,26,L,1 144 | C,27,L,1 145 | F,20,E,1 146 | F,21,E,1 147 | F,22,E,1 148 | G,3,E,1 149 | G,4,E,1 150 | G,5,E,1 151 | H,1,L,2 152 | H,2,L,2 153 | H,3,L,2 154 | H,4,L,2 155 | H,5,L,2 156 | J,20,E,1 157 | O,25,L,1 158 | P,22,L,3 159 | P,23,L,3 160 | P,24,L,3 161 | 162 | SECTION_COVER 163 | # Day, ShiftID, Requirement, Weight for under, Weight for over 164 | 0,E,5,100,1 165 | 0,L,4,100,1 166 | 1,E,7,100,1 167 | 1,L,4,100,1 168 | 2,E,5,100,1 169 | 2,L,6,100,1 170 | 3,E,6,100,1 171 | 3,L,4,100,1 172 | 4,E,7,100,1 173 | 4,L,3,100,1 174 | 5,E,6,100,1 175 | 5,L,3,100,1 176 | 6,E,6,100,1 177 | 6,L,4,100,1 178 | 7,E,6,100,1 179 | 7,L,4,100,1 180 | 8,E,6,100,1 181 | 8,L,4,100,1 182 | 9,E,6,100,1 183 | 9,L,6,100,1 184 | 10,E,5,100,1 185 | 10,L,4,100,1 186 | 11,E,6,100,1 187 | 11,L,4,100,1 188 | 12,E,6,100,1 189 | 12,L,4,100,1 190 | 13,E,6,100,1 191 | 13,L,4,100,1 192 | 14,E,7,100,1 193 | 14,L,3,100,1 194 | 15,E,8,100,1 195 | 15,L,4,100,1 196 | 16,E,6,100,1 197 | 16,L,5,100,1 198 | 17,E,7,100,1 199 | 17,L,4,100,1 200 | 18,E,7,100,1 201 | 18,L,4,100,1 202 | 19,E,6,100,1 203 | 19,L,4,100,1 204 | 20,E,6,100,1 205 | 20,L,4,100,1 206 | 21,E,6,100,1 207 | 21,L,4,100,1 208 | 22,E,5,100,1 209 | 22,L,4,100,1 210 | 23,E,6,100,1 211 | 23,L,5,100,1 212 | 24,E,6,100,1 213 | 24,L,4,100,1 214 | 25,E,4,100,1 215 | 25,L,4,100,1 216 | 26,E,6,100,1 217 | 26,L,6,100,1 218 | 27,E,7,100,1 219 | 27,L,5,100,1 220 | -------------------------------------------------------------------------------- /Benchmarks/Instance6.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 28 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | D,480,E 11 | L,480,E|D 12 | 13 | SECTION_STAFF 14 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 15 | A,E=28|D=28|L=0,8640,7680,5,2,2,2 16 | B,E=0|D=0|L=28,8640,7680,5,2,2,2 17 | C,E=0|D=28|L=28,8640,7680,5,2,2,2 18 | D,E=28|D=28|L=0,8640,7680,5,2,2,2 19 | E,E=28|D=28|L=28,8640,7680,5,2,2,2 20 | F,E=28|D=0|L=28,8640,7680,5,2,2,2 21 | G,E=28|D=0|L=28,8640,7680,5,2,2,2 22 | H,E=0|D=0|L=28,8640,7680,5,2,2,2 23 | I,E=0|D=28|L=0,8640,7680,5,2,2,2 24 | J,E=28|D=0|L=28,8640,7680,5,2,2,2 25 | K,E=0|D=28|L=28,8640,7680,6,2,3,2 26 | L,E=28|D=28|L=28,8640,7680,6,2,3,2 27 | M,E=28|D=28|L=28,8640,7680,6,2,3,2 28 | N,E=28|D=28|L=28,8640,7680,6,2,3,2 29 | O,E=28|D=28|L=28,8640,7680,6,2,3,2 30 | P,E=28|D=28|L=28,4320,3360,5,1,2,1 31 | Q,E=28|D=28|L=0,4320,3360,5,1,2,1 32 | R,E=28|D=28|L=28,4320,3360,5,1,2,1 33 | 34 | SECTION_DAYS_OFF 35 | # EmployeeID, DayIndexes (start at zero) 36 | A,18,19 37 | B,24,25 38 | C,22,23 39 | D,4,5 40 | E,23,27 41 | F,11,12 42 | G,16,20 43 | H,9,13 44 | I,17,27 45 | J,14,15 46 | K,15,16 47 | L,11,12 48 | M,2,23 49 | N,16,17 50 | O,0,1 51 | P,4,24 52 | Q,11,16 53 | R,26,27 54 | 55 | SECTION_SHIFT_ON_REQUESTS 56 | # EmployeeID, Day, ShiftID, Weight 57 | A,0,E,3 58 | A,1,E,3 59 | A,2,E,3 60 | A,3,E,3 61 | A,4,E,3 62 | B,0,L,2 63 | B,1,L,2 64 | B,2,L,2 65 | B,10,L,2 66 | B,18,L,2 67 | B,19,L,2 68 | B,20,L,2 69 | B,21,L,2 70 | B,22,L,2 71 | C,6,L,1 72 | C,7,L,1 73 | C,8,L,1 74 | C,9,L,1 75 | C,17,D,1 76 | C,18,D,1 77 | C,19,D,1 78 | C,20,D,1 79 | C,27,D,2 80 | D,12,D,3 81 | D,13,D,3 82 | E,1,L,1 83 | E,8,D,1 84 | E,9,D,1 85 | E,10,D,1 86 | E,11,D,1 87 | E,12,D,1 88 | F,2,L,1 89 | F,3,L,1 90 | F,4,L,1 91 | G,8,E,2 92 | G,9,E,2 93 | G,10,E,2 94 | G,11,E,2 95 | G,12,E,2 96 | G,21,L,2 97 | G,22,L,2 98 | G,23,L,2 99 | H,2,L,1 100 | H,15,L,3 101 | H,16,L,3 102 | H,17,L,3 103 | H,18,L,3 104 | H,22,L,1 105 | H,23,L,1 106 | H,24,L,1 107 | H,25,L,1 108 | I,3,D,3 109 | J,11,L,3 110 | J,12,L,3 111 | J,27,E,1 112 | K,3,L,1 113 | K,4,L,1 114 | K,5,L,1 115 | K,6,L,1 116 | K,7,L,1 117 | L,9,D,1 118 | L,10,D,1 119 | M,8,E,2 120 | M,9,E,2 121 | M,10,E,2 122 | M,11,E,2 123 | M,12,E,2 124 | N,19,E,3 125 | N,20,E,3 126 | O,3,D,1 127 | O,4,D,1 128 | O,13,L,3 129 | O,19,D,1 130 | O,20,D,1 131 | O,21,D,1 132 | O,22,D,1 133 | O,23,D,1 134 | P,8,D,2 135 | P,9,D,2 136 | Q,6,D,1 137 | Q,7,D,1 138 | Q,13,E,3 139 | Q,14,E,3 140 | Q,15,E,3 141 | Q,25,E,3 142 | R,3,L,2 143 | R,11,L,3 144 | 145 | SECTION_SHIFT_OFF_REQUESTS 146 | # EmployeeID, Day, ShiftID, Weight 147 | A,11,D,3 148 | A,12,D,3 149 | A,21,E,2 150 | A,22,E,2 151 | A,23,E,2 152 | A,24,E,2 153 | D,17,E,1 154 | D,18,E,1 155 | F,15,E,2 156 | G,0,E,1 157 | G,1,E,1 158 | I,19,D,2 159 | I,20,D,2 160 | I,21,D,2 161 | I,22,D,2 162 | J,2,E,3 163 | J,3,E,3 164 | J,4,E,3 165 | J,5,E,3 166 | J,17,E,2 167 | J,18,E,2 168 | J,19,E,2 169 | J,20,E,2 170 | L,2,E,3 171 | L,16,D,3 172 | L,17,D,3 173 | L,18,D,3 174 | L,25,L,2 175 | L,26,L,2 176 | L,27,L,2 177 | M,3,L,3 178 | M,16,E,3 179 | M,17,E,3 180 | M,18,E,3 181 | M,19,E,3 182 | M,20,E,3 183 | N,0,D,2 184 | N,1,D,2 185 | N,2,D,2 186 | N,3,D,2 187 | N,4,D,2 188 | N,9,L,3 189 | N,10,L,3 190 | N,11,L,3 191 | N,12,L,3 192 | N,13,L,3 193 | P,15,D,3 194 | R,23,L,2 195 | 196 | SECTION_COVER 197 | # Day, ShiftID, Requirement, Weight for under, Weight for over 198 | 0,E,3,100,1 199 | 0,D,4,100,1 200 | 0,L,4,100,1 201 | 1,E,4,100,1 202 | 1,D,4,100,1 203 | 1,L,2,100,1 204 | 2,E,4,100,1 205 | 2,D,4,100,1 206 | 2,L,5,100,1 207 | 3,E,4,100,1 208 | 3,D,3,100,1 209 | 3,L,4,100,1 210 | 4,E,4,100,1 211 | 4,D,6,100,1 212 | 4,L,5,100,1 213 | 5,E,3,100,1 214 | 5,D,3,100,1 215 | 5,L,2,100,1 216 | 6,E,3,100,1 217 | 6,D,3,100,1 218 | 6,L,4,100,1 219 | 7,E,4,100,1 220 | 7,D,3,100,1 221 | 7,L,3,100,1 222 | 8,E,3,100,1 223 | 8,D,4,100,1 224 | 8,L,2,100,1 225 | 9,E,4,100,1 226 | 9,D,2,100,1 227 | 9,L,6,100,1 228 | 10,E,3,100,1 229 | 10,D,2,100,1 230 | 10,L,3,100,1 231 | 11,E,5,100,1 232 | 11,D,3,100,1 233 | 11,L,2,100,1 234 | 12,E,4,100,1 235 | 12,D,3,100,1 236 | 12,L,6,100,1 237 | 13,E,2,100,1 238 | 13,D,4,100,1 239 | 13,L,4,100,1 240 | 14,E,4,100,1 241 | 14,D,4,100,1 242 | 14,L,4,100,1 243 | 15,E,2,100,1 244 | 15,D,3,100,1 245 | 15,L,4,100,1 246 | 16,E,5,100,1 247 | 16,D,4,100,1 248 | 16,L,6,100,1 249 | 17,E,2,100,1 250 | 17,D,2,100,1 251 | 17,L,2,100,1 252 | 18,E,5,100,1 253 | 18,D,3,100,1 254 | 18,L,5,100,1 255 | 19,E,3,100,1 256 | 19,D,3,100,1 257 | 19,L,3,100,1 258 | 20,E,2,100,1 259 | 20,D,2,100,1 260 | 20,L,3,100,1 261 | 21,E,4,100,1 262 | 21,D,4,100,1 263 | 21,L,3,100,1 264 | 22,E,6,100,1 265 | 22,D,3,100,1 266 | 22,L,3,100,1 267 | 23,E,4,100,1 268 | 23,D,5,100,1 269 | 23,L,2,100,1 270 | 24,E,4,100,1 271 | 24,D,3,100,1 272 | 24,L,4,100,1 273 | 25,E,2,100,1 274 | 25,D,2,100,1 275 | 25,L,2,100,1 276 | 26,E,5,100,1 277 | 26,D,4,100,1 278 | 26,L,4,100,1 279 | 27,E,5,100,1 280 | 27,D,5,100,1 281 | 27,L,4,100,1 282 | -------------------------------------------------------------------------------- /Benchmarks/Instance7.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 28 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | D,480,E 11 | L,480,E|D 12 | 13 | SECTION_STAFF 14 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 15 | A,E=28|D=28|L=0,8640,7560,5,2,2,2 16 | B,E=28|D=28|L=0,8640,7560,5,2,2,2 17 | C,E=28|D=28|L=9,8640,7560,5,2,2,2 18 | D,E=28|D=0|L=0,8640,7560,5,2,2,2 19 | E,E=28|D=28|L=9,8640,7560,5,2,2,2 20 | F,E=28|D=0|L=9,8640,7560,5,2,2,2 21 | G,E=28|D=0|L=0,8640,7560,5,2,2,2 22 | H,E=28|D=28|L=9,8640,7560,5,2,2,2 23 | I,E=28|D=28|L=0,8640,7560,5,2,2,2 24 | J,E=28|D=28|L=9,8640,7560,5,2,2,2 25 | K,E=28|D=28|L=9,8640,7560,6,2,3,2 26 | L,E=28|D=28|L=9,8640,7560,6,2,3,2 27 | M,E=28|D=28|L=9,8640,7560,6,2,3,2 28 | N,E=28|D=28|L=9,8640,7560,6,2,3,2 29 | O,E=28|D=28|L=0,8640,7560,6,2,3,2 30 | P,E=0|D=28|L=4,4320,3240,5,1,2,3 31 | Q,E=28|D=28|L=0,4320,3240,5,1,2,3 32 | R,E=28|D=28|L=4,4320,3240,5,1,2,3 33 | S,E=28|D=28|L=4,4320,3240,5,1,2,3 34 | T,E=28|D=28|L=0,4320,3240,5,1,2,3 35 | 36 | SECTION_DAYS_OFF 37 | # EmployeeID, DayIndexes (start at zero) 38 | A,15,16 39 | B,11,12 40 | C,1,6 41 | D,21,22 42 | E,13,14 43 | F,9,10 44 | G,26,27 45 | H,14,23 46 | I,11,12 47 | J,7,15 48 | K,9,25 49 | L,18,19 50 | M,3,4 51 | N,18,19 52 | O,7,18 53 | P,7,18 54 | Q,19,20 55 | R,7,24 56 | S,3,12 57 | T,25,26 58 | 59 | SECTION_SHIFT_ON_REQUESTS 60 | # EmployeeID, Day, ShiftID, Weight 61 | A,2,D,2 62 | A,3,D,2 63 | A,17,E,1 64 | A,25,E,3 65 | C,13,D,3 66 | C,14,D,3 67 | C,15,D,3 68 | C,20,E,1 69 | C,21,E,1 70 | D,8,E,3 71 | D,9,E,3 72 | D,10,E,3 73 | D,16,E,1 74 | D,17,E,1 75 | D,18,E,1 76 | D,19,E,1 77 | D,20,E,1 78 | E,0,L,1 79 | E,1,L,1 80 | E,2,L,1 81 | E,3,L,1 82 | E,7,L,3 83 | E,8,L,3 84 | E,9,L,3 85 | E,15,E,3 86 | E,23,L,1 87 | E,24,L,1 88 | E,25,L,1 89 | F,0,L,3 90 | F,1,L,3 91 | F,8,L,2 92 | H,16,D,1 93 | H,17,D,1 94 | H,18,D,1 95 | H,19,D,1 96 | I,19,E,2 97 | I,20,E,2 98 | I,21,E,2 99 | I,22,E,2 100 | J,26,E,3 101 | K,0,E,2 102 | K,20,E,3 103 | K,21,E,3 104 | K,22,E,3 105 | K,23,E,3 106 | K,24,E,3 107 | L,8,L,3 108 | L,9,L,3 109 | L,10,L,3 110 | L,11,L,3 111 | L,21,L,2 112 | L,22,L,2 113 | L,23,L,2 114 | L,24,L,2 115 | L,25,L,2 116 | M,0,E,3 117 | M,16,D,3 118 | M,17,D,3 119 | M,18,D,3 120 | N,1,L,3 121 | N,2,L,3 122 | N,3,L,3 123 | N,4,L,3 124 | N,5,L,3 125 | N,10,E,1 126 | N,11,E,1 127 | O,2,D,2 128 | O,3,D,2 129 | O,4,D,2 130 | O,5,D,2 131 | O,9,E,3 132 | O,10,E,3 133 | O,11,E,3 134 | P,2,L,3 135 | P,3,L,3 136 | P,4,L,3 137 | P,5,L,3 138 | P,6,L,3 139 | P,19,D,2 140 | P,20,D,2 141 | P,21,D,2 142 | P,22,D,2 143 | Q,1,D,2 144 | Q,2,D,2 145 | Q,3,D,2 146 | Q,4,D,2 147 | Q,5,D,2 148 | Q,26,D,1 149 | R,1,L,2 150 | R,2,L,2 151 | R,3,L,2 152 | R,4,L,2 153 | R,9,E,3 154 | R,10,E,3 155 | R,22,D,1 156 | S,1,D,1 157 | S,2,D,1 158 | S,15,D,2 159 | S,16,D,2 160 | S,17,D,2 161 | S,18,D,2 162 | T,8,D,3 163 | T,9,D,3 164 | T,10,D,3 165 | 166 | SECTION_SHIFT_OFF_REQUESTS 167 | # EmployeeID, Day, ShiftID, Weight 168 | B,2,D,2 169 | B,6,E,1 170 | B,7,E,1 171 | B,8,E,1 172 | B,9,E,1 173 | B,13,D,2 174 | B,14,D,2 175 | B,15,D,2 176 | B,16,D,2 177 | B,17,D,2 178 | D,2,E,2 179 | D,3,E,2 180 | D,4,E,2 181 | F,23,L,3 182 | F,24,L,3 183 | G,2,E,2 184 | G,3,E,2 185 | G,7,E,3 186 | G,8,E,3 187 | G,9,E,3 188 | G,10,E,3 189 | G,14,E,2 190 | G,15,E,2 191 | G,16,E,2 192 | G,17,E,2 193 | G,18,E,2 194 | H,1,D,2 195 | H,2,D,2 196 | I,3,E,1 197 | I,4,E,1 198 | I,5,E,1 199 | J,0,E,1 200 | J,1,E,1 201 | J,2,E,1 202 | J,3,E,1 203 | J,4,E,1 204 | J,16,E,1 205 | J,17,E,1 206 | J,18,E,1 207 | K,4,L,3 208 | K,10,D,3 209 | K,11,D,3 210 | K,12,D,3 211 | K,13,D,3 212 | M,7,L,1 213 | M,8,L,1 214 | M,9,L,1 215 | M,10,L,1 216 | M,11,L,1 217 | Q,10,D,3 218 | Q,11,D,3 219 | Q,12,D,3 220 | Q,16,D,3 221 | R,14,L,3 222 | R,15,L,3 223 | R,16,L,3 224 | S,23,L,3 225 | S,24,L,3 226 | S,25,L,3 227 | T,20,D,1 228 | T,21,D,1 229 | T,22,D,1 230 | T,23,D,1 231 | T,24,D,1 232 | 233 | SECTION_COVER 234 | # Day, ShiftID, Requirement, Weight for under, Weight for over 235 | 0,E,4,100,1 236 | 0,D,6,100,1 237 | 0,L,3,100,1 238 | 1,E,4,100,1 239 | 1,D,6,100,1 240 | 1,L,1,100,1 241 | 2,E,4,100,1 242 | 2,D,6,100,1 243 | 2,L,2,100,1 244 | 3,E,4,100,1 245 | 3,D,5,100,1 246 | 3,L,1,100,1 247 | 4,E,3,100,1 248 | 4,D,7,100,1 249 | 4,L,2,100,1 250 | 5,E,4,100,1 251 | 5,D,4,100,1 252 | 5,L,3,100,1 253 | 6,E,4,100,1 254 | 6,D,7,100,1 255 | 6,L,2,100,1 256 | 7,E,4,100,1 257 | 7,D,5,100,1 258 | 7,L,2,100,1 259 | 8,E,4,100,1 260 | 8,D,6,100,1 261 | 8,L,2,100,1 262 | 9,E,3,100,1 263 | 9,D,4,100,1 264 | 9,L,2,100,1 265 | 10,E,2,100,1 266 | 10,D,5,100,1 267 | 10,L,2,100,1 268 | 11,E,6,100,1 269 | 11,D,5,100,1 270 | 11,L,1,100,1 271 | 12,E,4,100,1 272 | 12,D,3,100,1 273 | 12,L,2,100,1 274 | 13,E,6,100,1 275 | 13,D,5,100,1 276 | 13,L,2,100,1 277 | 14,E,5,100,1 278 | 14,D,5,100,1 279 | 14,L,1,100,1 280 | 15,E,4,100,1 281 | 15,D,6,100,1 282 | 15,L,2,100,1 283 | 16,E,3,100,1 284 | 16,D,3,100,1 285 | 16,L,2,100,1 286 | 17,E,4,100,1 287 | 17,D,5,100,1 288 | 17,L,2,100,1 289 | 18,E,4,100,1 290 | 18,D,5,100,1 291 | 18,L,3,100,1 292 | 19,E,6,100,1 293 | 19,D,6,100,1 294 | 19,L,2,100,1 295 | 20,E,6,100,1 296 | 20,D,3,100,1 297 | 20,L,2,100,1 298 | 21,E,6,100,1 299 | 21,D,3,100,1 300 | 21,L,1,100,1 301 | 22,E,4,100,1 302 | 22,D,5,100,1 303 | 22,L,4,100,1 304 | 23,E,4,100,1 305 | 23,D,5,100,1 306 | 23,L,3,100,1 307 | 24,E,5,100,1 308 | 24,D,5,100,1 309 | 24,L,3,100,1 310 | 25,E,4,100,1 311 | 25,D,5,100,1 312 | 25,L,2,100,1 313 | 26,E,2,100,1 314 | 26,D,2,100,1 315 | 26,L,2,100,1 316 | 27,E,5,100,1 317 | 27,D,7,100,1 318 | 27,L,2,100,1 319 | -------------------------------------------------------------------------------- /Benchmarks/Instance8.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 28 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | D,480,E 11 | L,480,E|D 12 | N,480,E|D|L 13 | 14 | SECTION_STAFF 15 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 16 | A,E=0|D=28|L=0|N=4,8640,8160,5,2,2,2 17 | B,E=14|D=28|L=0|N=4,8640,8160,5,2,2,2 18 | C,E=0|D=28|L=0|N=4,8640,8160,5,2,2,2 19 | D,E=0|D=28|L=14|N=0,8640,8160,5,2,2,2 20 | E,E=14|D=28|L=0|N=4,8640,8160,5,2,2,2 21 | F,E=14|D=28|L=0|N=4,8640,8160,5,2,2,2 22 | G,E=0|D=28|L=0|N=4,8640,8160,5,2,2,2 23 | H,E=14|D=28|L=0|N=0,8640,8160,5,2,2,2 24 | I,E=14|D=28|L=0|N=4,8640,8160,5,2,2,2 25 | J,E=0|D=28|L=14|N=4,8640,8160,5,2,2,2 26 | K,E=14|D=28|L=0|N=4,8640,8160,5,2,2,2 27 | L,E=14|D=28|L=0|N=4,8640,8160,5,2,2,2 28 | M,E=0|D=28|L=14|N=4,8640,8160,5,2,2,2 29 | N,E=14|D=28|L=14|N=4,8640,8160,5,2,2,2 30 | O,E=14|D=28|L=14|N=0,8640,8160,5,2,2,2 31 | P,E=14|D=28|L=0|N=0,8640,8160,5,2,2,2 32 | Q,E=14|D=28|L=14|N=4,8640,8160,6,2,3,2 33 | R,E=0|D=28|L=0|N=0,8640,8160,6,2,3,2 34 | S,E=0|D=28|L=14|N=0,8640,8160,6,2,3,2 35 | T,E=14|D=28|L=14|N=0,8640,8160,6,2,3,2 36 | U,E=14|D=28|L=0|N=4,8640,8160,6,2,3,2 37 | V,E=14|D=28|L=14|N=4,8640,8160,6,2,3,2 38 | W,E=14|D=28|L=0|N=4,8640,8160,6,2,3,2 39 | X,E=0|D=28|L=0|N=2,5160,4680,5,1,2,3 40 | Y,E=0|D=28|L=8|N=0,5160,4680,5,1,2,3 41 | Z,E=8|D=28|L=8|N=2,5160,4680,5,1,2,3 42 | AA,E=0|D=28|L=8|N=2,5160,4680,5,1,2,3 43 | AB,E=8|D=28|L=8|N=2,5160,4680,5,1,2,3 44 | AC,E=0|D=28|L=0|N=1,3420,2940,5,1,1,3 45 | AD,E=0|D=28|L=5|N=1,3420,2940,5,1,1,3 46 | 47 | SECTION_DAYS_OFF 48 | # EmployeeID, DayIndexes (start at zero) 49 | A,1,2 50 | B,4,9 51 | C,16,17 52 | D,22,26 53 | E,2,14 54 | F,26,27 55 | G,13,14 56 | H,11,12 57 | I,13,14 58 | J,3,10 59 | K,14,15 60 | L,9,14 61 | M,0,12 62 | N,8,27 63 | O,14,24 64 | P,7,8 65 | Q,3,12 66 | R,4,7 67 | S,4,24 68 | T,7,27 69 | U,21,22 70 | V,10,18 71 | W,1,19 72 | X,3,4 73 | Y,5,25 74 | Z,17,18 75 | AA,23,24 76 | AB,5,6 77 | AC,15,16 78 | AD,15,16 79 | 80 | SECTION_SHIFT_ON_REQUESTS 81 | # EmployeeID, Day, ShiftID, Weight 82 | A,4,N,2 83 | A,5,N,2 84 | A,6,N,2 85 | A,12,N,3 86 | A,13,N,3 87 | C,0,D,1 88 | C,1,D,1 89 | C,2,D,1 90 | C,3,D,1 91 | C,8,D,3 92 | C,9,D,3 93 | C,10,D,3 94 | C,11,D,3 95 | D,1,L,2 96 | D,2,L,2 97 | D,3,L,2 98 | D,23,D,2 99 | D,24,D,2 100 | E,4,D,2 101 | E,5,D,2 102 | E,6,D,2 103 | E,27,D,2 104 | F,1,D,2 105 | F,2,D,2 106 | F,17,N,1 107 | F,18,N,1 108 | F,19,N,1 109 | F,25,D,2 110 | G,1,D,1 111 | G,2,D,1 112 | G,3,D,1 113 | H,14,E,3 114 | H,15,E,3 115 | I,0,N,1 116 | I,1,N,1 117 | I,2,N,1 118 | J,5,L,1 119 | J,6,L,1 120 | J,17,L,2 121 | J,18,L,2 122 | J,19,L,2 123 | J,20,L,2 124 | K,17,D,3 125 | K,18,D,3 126 | K,19,D,3 127 | L,16,D,3 128 | L,17,D,3 129 | L,18,D,3 130 | L,19,D,3 131 | L,26,N,2 132 | L,27,N,2 133 | M,21,N,3 134 | M,22,N,3 135 | M,26,N,1 136 | N,2,D,1 137 | N,3,D,1 138 | N,4,D,1 139 | N,5,D,1 140 | N,6,D,1 141 | N,19,N,2 142 | N,20,N,2 143 | N,21,N,2 144 | O,0,L,2 145 | O,1,L,2 146 | O,2,L,2 147 | O,3,L,2 148 | O,4,L,2 149 | O,16,E,3 150 | O,17,E,3 151 | O,18,E,3 152 | P,15,D,2 153 | P,16,D,2 154 | P,17,D,2 155 | P,18,D,2 156 | Q,4,L,2 157 | Q,11,D,2 158 | R,19,D,1 159 | R,20,D,1 160 | R,21,D,1 161 | R,22,D,1 162 | R,23,D,1 163 | S,11,L,3 164 | S,12,L,3 165 | S,13,L,3 166 | S,14,L,3 167 | T,9,L,1 168 | T,10,L,1 169 | T,14,D,3 170 | T,15,D,3 171 | T,16,D,3 172 | T,17,D,3 173 | U,0,N,3 174 | U,7,D,1 175 | U,8,D,1 176 | U,9,D,1 177 | U,10,D,1 178 | U,14,E,3 179 | U,15,E,3 180 | U,16,E,3 181 | U,24,N,3 182 | W,4,D,2 183 | W,5,D,2 184 | W,6,D,2 185 | W,7,D,2 186 | X,16,D,1 187 | Y,0,D,1 188 | Z,0,N,2 189 | Z,10,L,2 190 | AA,12,D,1 191 | AA,17,D,3 192 | AA,18,D,3 193 | AA,19,D,3 194 | AA,20,D,3 195 | AB,24,E,3 196 | AB,25,E,3 197 | AB,26,E,3 198 | AB,27,E,3 199 | AC,0,N,3 200 | AC,1,N,3 201 | AC,2,N,3 202 | AC,3,N,3 203 | AC,4,N,3 204 | AC,8,N,2 205 | AC,9,N,2 206 | AC,22,D,1 207 | AC,23,D,1 208 | AC,24,D,1 209 | AC,25,D,1 210 | AD,3,D,3 211 | AD,4,D,3 212 | AD,5,D,3 213 | AD,6,D,3 214 | AD,7,D,3 215 | AD,18,N,1 216 | AD,19,N,1 217 | AD,20,N,1 218 | AD,21,N,1 219 | AD,25,L,2 220 | AD,26,L,2 221 | 222 | SECTION_SHIFT_OFF_REQUESTS 223 | # EmployeeID, Day, ShiftID, Weight 224 | B,17,D,2 225 | B,18,D,2 226 | B,19,D,2 227 | B,20,D,2 228 | D,11,L,3 229 | D,12,L,3 230 | F,6,E,2 231 | G,16,N,1 232 | G,17,N,1 233 | G,18,N,1 234 | I,11,N,1 235 | I,20,N,1 236 | I,21,N,1 237 | I,22,N,1 238 | I,23,N,1 239 | J,26,D,2 240 | J,27,D,2 241 | K,6,E,2 242 | K,7,E,2 243 | K,8,E,2 244 | L,11,D,1 245 | L,12,D,1 246 | M,3,N,1 247 | M,4,N,1 248 | M,5,N,1 249 | M,6,N,1 250 | O,8,L,3 251 | O,9,L,3 252 | O,10,L,3 253 | O,11,L,3 254 | P,9,E,2 255 | Q,18,N,1 256 | Q,19,N,1 257 | Q,20,N,1 258 | Q,21,N,1 259 | R,9,D,3 260 | R,10,D,3 261 | R,11,D,3 262 | R,12,D,3 263 | S,0,D,2 264 | S,1,D,2 265 | S,2,D,2 266 | S,3,D,2 267 | T,0,E,3 268 | T,1,E,3 269 | T,2,E,3 270 | T,3,E,3 271 | T,22,L,3 272 | T,23,L,3 273 | T,24,L,3 274 | T,25,L,3 275 | T,26,L,3 276 | V,12,L,1 277 | V,13,L,1 278 | W,20,E,3 279 | W,21,E,3 280 | W,22,E,3 281 | X,6,D,2 282 | X,7,D,2 283 | X,8,D,2 284 | X,9,D,2 285 | X,21,D,1 286 | X,22,D,1 287 | Y,6,L,3 288 | Y,7,L,3 289 | Y,8,L,3 290 | Y,9,L,3 291 | Y,10,L,3 292 | Y,17,D,1 293 | Y,18,D,1 294 | Y,19,D,1 295 | Z,14,N,1 296 | Z,15,N,1 297 | Z,22,D,1 298 | AA,3,L,3 299 | AA,4,L,3 300 | AB,0,D,1 301 | AB,1,D,1 302 | AB,2,D,1 303 | AB,10,E,1 304 | AB,11,E,1 305 | AB,12,E,1 306 | AB,13,E,1 307 | AB,14,E,1 308 | AC,17,N,2 309 | AC,18,N,2 310 | 311 | SECTION_COVER 312 | # Day, ShiftID, Requirement, Weight for under, Weight for over 313 | 0,E,5,100,1 314 | 0,D,5,100,1 315 | 0,L,7,100,1 316 | 0,N,2,100,1 317 | 1,E,6,100,1 318 | 1,D,5,100,1 319 | 1,L,5,100,1 320 | 1,N,3,100,1 321 | 2,E,5,100,1 322 | 2,D,3,100,1 323 | 2,L,4,100,1 324 | 2,N,3,100,1 325 | 3,E,5,100,1 326 | 3,D,6,100,1 327 | 3,L,7,100,1 328 | 3,N,3,100,1 329 | 4,E,5,100,1 330 | 4,D,3,100,1 331 | 4,L,6,100,1 332 | 4,N,3,100,1 333 | 5,E,5,100,1 334 | 5,D,4,100,1 335 | 5,L,5,100,1 336 | 5,N,5,100,1 337 | 6,E,5,100,1 338 | 6,D,3,100,1 339 | 6,L,5,100,1 340 | 6,N,3,100,1 341 | 7,E,5,100,1 342 | 7,D,4,100,1 343 | 7,L,6,100,1 344 | 7,N,4,100,1 345 | 8,E,4,100,1 346 | 8,D,5,100,1 347 | 8,L,5,100,1 348 | 8,N,2,100,1 349 | 9,E,5,100,1 350 | 9,D,7,100,1 351 | 9,L,6,100,1 352 | 9,N,3,100,1 353 | 10,E,6,100,1 354 | 10,D,3,100,1 355 | 10,L,5,100,1 356 | 10,N,2,100,1 357 | 11,E,5,100,1 358 | 11,D,5,100,1 359 | 11,L,4,100,1 360 | 11,N,3,100,1 361 | 12,E,5,100,1 362 | 12,D,5,100,1 363 | 12,L,5,100,1 364 | 12,N,3,100,1 365 | 13,E,5,100,1 366 | 13,D,4,100,1 367 | 13,L,5,100,1 368 | 13,N,2,100,1 369 | 14,E,5,100,1 370 | 14,D,5,100,1 371 | 14,L,5,100,1 372 | 14,N,4,100,1 373 | 15,E,4,100,1 374 | 15,D,5,100,1 375 | 15,L,5,100,1 376 | 15,N,3,100,1 377 | 16,E,3,100,1 378 | 16,D,5,100,1 379 | 16,L,5,100,1 380 | 16,N,4,100,1 381 | 17,E,3,100,1 382 | 17,D,6,100,1 383 | 17,L,5,100,1 384 | 17,N,3,100,1 385 | 18,E,6,100,1 386 | 18,D,6,100,1 387 | 18,L,3,100,1 388 | 18,N,3,100,1 389 | 19,E,4,100,1 390 | 19,D,7,100,1 391 | 19,L,5,100,1 392 | 19,N,3,100,1 393 | 20,E,5,100,1 394 | 20,D,4,100,1 395 | 20,L,7,100,1 396 | 20,N,3,100,1 397 | 21,E,5,100,1 398 | 21,D,5,100,1 399 | 21,L,4,100,1 400 | 21,N,3,100,1 401 | 22,E,5,100,1 402 | 22,D,5,100,1 403 | 22,L,4,100,1 404 | 22,N,3,100,1 405 | 23,E,3,100,1 406 | 23,D,5,100,1 407 | 23,L,5,100,1 408 | 23,N,3,100,1 409 | 24,E,3,100,1 410 | 24,D,3,100,1 411 | 24,L,6,100,1 412 | 24,N,3,100,1 413 | 25,E,3,100,1 414 | 25,D,5,100,1 415 | 25,L,4,100,1 416 | 25,N,4,100,1 417 | 26,E,5,100,1 418 | 26,D,4,100,1 419 | 26,L,4,100,1 420 | 26,N,4,100,1 421 | 27,E,3,100,1 422 | 27,D,2,100,1 423 | 27,L,2,100,1 424 | 27,N,2,100,1 425 | -------------------------------------------------------------------------------- /Benchmarks/Instance9.txt: -------------------------------------------------------------------------------- 1 | # This is a comment. Comments start with # 2 | SECTION_HORIZON 3 | # All instances start on a Monday 4 | # The horizon length in days: 5 | 28 6 | 7 | SECTION_SHIFTS 8 | # ShiftID, Length in mins, Shifts which cannot follow this shift | separated 9 | E,480, 10 | D,480,E 11 | L,480,E|D 12 | N,600,E|D|L 13 | 14 | SECTION_STAFF 15 | # ID, MaxShifts, MaxTotalMinutes, MinTotalMinutes, MaxConsecutiveShifts, MinConsecutiveShifts, MinConsecutiveDaysOff, MaxWeekends 16 | A,E=28|D=28|L=28|N=5,8160,7080,5,2,2,2 17 | B,E=28|D=28|L=28|N=0,8160,7080,5,2,2,2 18 | C,E=28|D=28|L=28|N=0,8160,7080,5,2,2,2 19 | D,E=0|D=28|L=28|N=5,8160,7080,5,2,2,2 20 | E,E=28|D=28|L=0|N=0,8160,7080,5,2,2,2 21 | F,E=28|D=28|L=0|N=5,8160,7080,5,2,2,2 22 | G,E=28|D=28|L=0|N=0,8160,7080,5,2,2,2 23 | H,E=28|D=28|L=28|N=5,8160,7080,5,2,2,2 24 | I,E=28|D=28|L=28|N=5,8160,7080,5,2,2,2 25 | J,E=28|D=28|L=28|N=5,8160,7080,5,2,2,2 26 | K,E=28|D=0|L=28|N=4,6480,5400,4,2,3,2 27 | L,E=28|D=0|L=28|N=4,6480,5400,4,2,3,2 28 | M,E=0|D=28|L=28|N=4,6480,5400,4,2,3,2 29 | N,E=28|D=28|L=28|N=4,6480,5400,4,2,3,2 30 | O,E=28|D=28|L=28|N=4,6480,5400,4,2,3,2 31 | P,E=28|D=28|L=0|N=4,6480,5400,4,2,3,2 32 | Q,E=0|D=28|L=28|N=0,5160,4080,5,1,2,3 33 | R,E=28|D=28|L=28|N=3,5160,4080,5,1,2,3 34 | S,E=28|D=28|L=0|N=3,5160,4080,5,1,2,3 35 | T,E=0|D=28|L=28|N=3,5160,4080,5,1,2,3 36 | U,E=28|D=28|L=28|N=3,5160,4080,5,1,2,3 37 | V,E=28|D=28|L=0|N=3,5160,4080,5,1,2,3 38 | W,E=0|D=0|L=28|N=0,5160,4080,5,1,2,3 39 | X,E=28|D=28|L=28|N=3,5160,4080,5,1,2,3 40 | Y,E=28|D=28|L=28|N=3,5160,4080,5,1,2,3 41 | Z,E=28|D=28|L=0|N=0,5160,4080,5,1,2,3 42 | AA,E=0|D=0|L=28|N=2,3420,2340,5,1,1,3 43 | AB,E=0|D=28|L=0|N=2,3420,2340,5,1,1,3 44 | AC,E=0|D=28|L=28|N=2,3420,2340,5,1,1,3 45 | AD,E=28|D=28|L=0|N=2,3420,2340,5,1,1,3 46 | AE,E=28|D=28|L=28|N=2,3420,2340,5,1,1,3 47 | AF,E=28|D=28|L=28|N=2,3420,2340,5,1,1,3 48 | AG,E=28|D=28|L=28|N=0,3420,2340,5,1,1,3 49 | AH,E=28|D=28|L=28|N=2,3420,2340,5,1,1,3 50 | AI,E=0|D=28|L=28|N=0,3420,2340,5,1,1,3 51 | AJ,E=28|D=0|L=28|N=2,3420,2340,5,1,1,3 52 | 53 | SECTION_DAYS_OFF 54 | # EmployeeID, DayIndexes (start at zero) 55 | A,15,18 56 | B,9,10 57 | C,24,25 58 | D,13,14 59 | E,17,23 60 | F,14,15 61 | G,2,3 62 | H,18,19 63 | I,8,18 64 | J,3,22 65 | K,1,25 66 | L,6,7 67 | M,21,22 68 | N,22,25 69 | O,11,12 70 | P,7,12 71 | Q,24,25 72 | R,14,15 73 | S,3,4 74 | T,25,26 75 | U,3,14 76 | V,22,23 77 | W,0,14 78 | X,16,21 79 | Y,11,12 80 | Z,7,8 81 | AA,5,6 82 | AB,0,2 83 | AC,7,15 84 | AD,24,25 85 | AE,13,14 86 | AF,1,2 87 | AG,1,2 88 | AH,5,6 89 | AI,13,14 90 | AJ,0,10 91 | 92 | SECTION_SHIFT_ON_REQUESTS 93 | # EmployeeID, Day, ShiftID, Weight 94 | A,8,L,3 95 | A,9,L,3 96 | A,10,L,3 97 | B,0,D,1 98 | B,1,D,1 99 | C,22,D,1 100 | D,1,L,2 101 | D,17,L,1 102 | D,18,L,1 103 | E,1,D,3 104 | E,2,D,3 105 | E,21,E,3 106 | E,22,E,3 107 | F,8,E,3 108 | F,9,E,3 109 | F,10,E,3 110 | F,16,E,3 111 | F,17,E,3 112 | F,18,E,3 113 | F,19,E,3 114 | H,20,L,3 115 | H,21,L,3 116 | I,16,L,1 117 | J,2,N,1 118 | J,9,L,3 119 | J,10,L,3 120 | J,11,L,3 121 | J,12,L,3 122 | J,16,L,2 123 | J,17,L,2 124 | J,18,L,2 125 | J,19,L,2 126 | J,26,E,1 127 | J,27,E,1 128 | L,17,L,1 129 | L,18,L,1 130 | L,19,L,1 131 | M,0,L,3 132 | M,1,L,3 133 | M,5,L,1 134 | M,6,L,1 135 | M,10,L,1 136 | M,16,D,1 137 | M,17,D,1 138 | M,18,D,1 139 | M,19,D,1 140 | M,20,D,1 141 | M,26,N,3 142 | M,27,N,3 143 | O,22,N,2 144 | O,23,N,2 145 | O,24,N,2 146 | R,3,N,3 147 | R,4,N,3 148 | R,5,N,3 149 | R,6,N,3 150 | R,7,N,3 151 | R,13,N,1 152 | R,21,L,2 153 | R,22,L,2 154 | R,23,L,2 155 | R,24,L,2 156 | R,25,L,2 157 | T,6,N,2 158 | T,7,N,2 159 | T,8,N,2 160 | T,9,N,2 161 | T,22,N,3 162 | T,23,N,3 163 | T,24,N,3 164 | U,17,E,3 165 | U,18,E,3 166 | U,19,E,3 167 | U,20,E,3 168 | V,3,D,3 169 | V,7,D,1 170 | W,1,L,3 171 | W,2,L,3 172 | W,3,L,3 173 | W,19,L,2 174 | W,20,L,2 175 | X,5,L,1 176 | X,6,L,1 177 | X,7,L,1 178 | X,8,L,1 179 | X,9,L,1 180 | Y,15,L,1 181 | Y,22,D,1 182 | Y,23,D,1 183 | Y,24,D,1 184 | Z,1,D,3 185 | Z,2,D,3 186 | Z,3,D,3 187 | Z,4,D,3 188 | Z,10,E,1 189 | Z,11,E,1 190 | Z,25,D,3 191 | Z,26,D,3 192 | Z,27,D,3 193 | AA,7,N,2 194 | AA,8,N,2 195 | AA,9,N,2 196 | AA,10,N,2 197 | AB,22,N,3 198 | AB,23,N,3 199 | AB,24,N,3 200 | AB,25,N,3 201 | AB,26,N,3 202 | AC,0,D,2 203 | AC,1,D,2 204 | AC,6,N,2 205 | AC,24,N,1 206 | AC,25,N,1 207 | AC,26,N,1 208 | AD,6,E,1 209 | AD,7,E,1 210 | AD,8,E,1 211 | AF,12,E,1 212 | AF,13,E,1 213 | AF,14,E,1 214 | AF,22,L,3 215 | AG,20,E,3 216 | AG,21,E,3 217 | AG,22,E,3 218 | AH,0,E,1 219 | AH,1,E,1 220 | AH,2,E,1 221 | AI,3,D,2 222 | AI,10,D,2 223 | AI,11,D,2 224 | AI,12,D,2 225 | AI,21,D,3 226 | AI,22,D,3 227 | AJ,6,L,1 228 | AJ,7,L,1 229 | AJ,14,L,1 230 | AJ,15,L,1 231 | AJ,16,L,1 232 | AJ,17,L,1 233 | AJ,18,L,1 234 | AJ,22,L,3 235 | AJ,23,L,3 236 | AJ,24,L,3 237 | AJ,25,L,3 238 | 239 | SECTION_SHIFT_OFF_REQUESTS 240 | # EmployeeID, Day, ShiftID, Weight 241 | A,20,D,2 242 | A,21,D,2 243 | A,22,D,2 244 | B,16,E,2 245 | B,17,E,2 246 | B,18,E,2 247 | B,19,E,2 248 | C,7,E,2 249 | C,8,E,2 250 | C,9,E,2 251 | C,10,E,2 252 | D,6,D,2 253 | D,7,D,2 254 | D,8,D,2 255 | E,10,D,3 256 | E,14,E,3 257 | E,26,D,1 258 | G,19,E,1 259 | H,1,D,3 260 | H,2,D,3 261 | H,3,D,3 262 | H,4,D,3 263 | H,12,E,2 264 | H,13,E,2 265 | H,14,E,2 266 | I,9,N,1 267 | I,10,N,1 268 | K,3,E,3 269 | K,4,E,3 270 | K,11,E,1 271 | K,12,E,1 272 | K,13,E,1 273 | K,14,E,1 274 | K,15,E,1 275 | L,8,N,1 276 | L,9,N,1 277 | L,10,N,1 278 | N,3,D,3 279 | N,4,D,3 280 | N,11,E,1 281 | O,14,E,1 282 | O,15,E,1 283 | O,16,E,1 284 | Q,14,L,3 285 | Q,15,L,3 286 | Q,16,L,3 287 | T,14,D,2 288 | U,9,N,1 289 | U,10,N,1 290 | U,11,N,1 291 | V,14,D,3 292 | V,15,D,3 293 | V,16,D,3 294 | V,17,D,3 295 | W,24,L,2 296 | W,25,L,2 297 | Y,3,L,1 298 | Y,4,L,1 299 | Y,5,L,1 300 | Z,17,E,2 301 | Z,18,E,2 302 | Z,19,E,2 303 | AA,18,N,3 304 | AC,16,N,1 305 | AC,17,N,1 306 | AC,18,N,1 307 | AC,19,N,1 308 | AC,20,N,1 309 | AE,0,L,1 310 | AE,1,L,1 311 | AE,2,L,1 312 | AE,3,L,1 313 | AE,15,L,3 314 | AE,16,L,3 315 | AE,17,L,3 316 | AE,18,L,3 317 | AE,19,L,3 318 | AE,25,L,3 319 | AE,26,L,3 320 | AE,27,L,3 321 | AG,3,L,2 322 | AG,4,L,2 323 | AG,5,L,2 324 | AH,13,L,2 325 | AH,14,L,2 326 | AH,15,L,2 327 | AH,16,L,2 328 | AH,24,D,1 329 | 330 | SECTION_COVER 331 | # Day, ShiftID, Requirement, Weight for under, Weight for over 332 | 0,E,2,100,1 333 | 0,D,5,100,1 334 | 0,L,6,100,1 335 | 0,N,2,100,1 336 | 1,E,3,100,1 337 | 1,D,6,100,1 338 | 1,L,4,100,1 339 | 1,N,2,100,1 340 | 2,E,4,100,1 341 | 2,D,4,100,1 342 | 2,L,5,100,1 343 | 2,N,1,100,1 344 | 3,E,5,100,1 345 | 3,D,5,100,1 346 | 3,L,5,100,1 347 | 3,N,1,100,1 348 | 4,E,3,100,1 349 | 4,D,3,100,1 350 | 4,L,4,100,1 351 | 4,N,2,100,1 352 | 5,E,4,100,1 353 | 5,D,2,100,1 354 | 5,L,5,100,1 355 | 5,N,2,100,1 356 | 6,E,3,100,1 357 | 6,D,4,100,1 358 | 6,L,4,100,1 359 | 6,N,1,100,1 360 | 7,E,3,100,1 361 | 7,D,4,100,1 362 | 7,L,4,100,1 363 | 7,N,3,100,1 364 | 8,E,3,100,1 365 | 8,D,4,100,1 366 | 8,L,3,100,1 367 | 8,N,3,100,1 368 | 9,E,4,100,1 369 | 9,D,4,100,1 370 | 9,L,4,100,1 371 | 9,N,3,100,1 372 | 10,E,4,100,1 373 | 10,D,2,100,1 374 | 10,L,2,100,1 375 | 10,N,4,100,1 376 | 11,E,4,100,1 377 | 11,D,6,100,1 378 | 11,L,5,100,1 379 | 11,N,2,100,1 380 | 12,E,5,100,1 381 | 12,D,2,100,1 382 | 12,L,4,100,1 383 | 12,N,4,100,1 384 | 13,E,4,100,1 385 | 13,D,3,100,1 386 | 13,L,4,100,1 387 | 13,N,3,100,1 388 | 14,E,4,100,1 389 | 14,D,2,100,1 390 | 14,L,3,100,1 391 | 14,N,3,100,1 392 | 15,E,4,100,1 393 | 15,D,3,100,1 394 | 15,L,4,100,1 395 | 15,N,3,100,1 396 | 16,E,2,100,1 397 | 16,D,3,100,1 398 | 16,L,4,100,1 399 | 16,N,3,100,1 400 | 17,E,3,100,1 401 | 17,D,3,100,1 402 | 17,L,5,100,1 403 | 17,N,2,100,1 404 | 18,E,3,100,1 405 | 18,D,4,100,1 406 | 18,L,5,100,1 407 | 18,N,3,100,1 408 | 19,E,2,100,1 409 | 19,D,4,100,1 410 | 19,L,4,100,1 411 | 19,N,2,100,1 412 | 20,E,5,100,1 413 | 20,D,4,100,1 414 | 20,L,3,100,1 415 | 20,N,3,100,1 416 | 21,E,6,100,1 417 | 21,D,5,100,1 418 | 21,L,6,100,1 419 | 21,N,3,100,1 420 | 22,E,4,100,1 421 | 22,D,3,100,1 422 | 22,L,4,100,1 423 | 22,N,2,100,1 424 | 23,E,4,100,1 425 | 23,D,5,100,1 426 | 23,L,5,100,1 427 | 23,N,2,100,1 428 | 24,E,5,100,1 429 | 24,D,5,100,1 430 | 24,L,5,100,1 431 | 24,N,2,100,1 432 | 25,E,5,100,1 433 | 25,D,4,100,1 434 | 25,L,5,100,1 435 | 25,N,3,100,1 436 | 26,E,3,100,1 437 | 26,D,6,100,1 438 | 26,L,3,100,1 439 | 26,N,5,100,1 440 | 27,E,4,100,1 441 | 27,D,7,100,1 442 | 27,L,6,100,1 443 | 27,N,3,100,1 444 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Explainable Constraint Solving - A Hands-On Tutorial 2 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.10694139.svg)](https://doi.org/10.5281/zenodo.10694139) 3 | 4 | ### by Ignace Bleukx, Dimos Tsouros and Tias Guns 5 | 6 | This repository contains the code and runnable notebook for our Explainable Constraint Solving tutorials and talks. 7 | 8 | ### Latest: ECAI2024 version 9 | 10 | - Tutorial notebook (PDF will be added later): https://github.com/CPMpy/XCP-explain/blob/main/ecai2024_presentation.ipynb 11 | - Part 1 Google Colab: https://colab.research.google.com/github/CPMpy/XCP-explain/blob/main/ecai2024_practice_part1.ipynb 12 | - Part 2 Google Colab: : https://colab.research.google.com/github/CPMpy/XCP-explain/blob/main/ecai2024_practice_part2.ipynb 13 | - Part 3 Google Colab: https://colab.research.google.com/github/CPMpy/XCP-explain/blob/main/ecai2024_practice_part3.ipynb 14 | 15 | Explainable constraint solving is a sub-field of explainable AI (XAI) concerned with explaining constraint (optimization) problems. 16 | Although constraint models are explicit: they are written down in terms of individual constraints that need to be satisfied, and the solution to such models can be non-trivial to understand. 17 | 18 | Driven by the use-case of nurse scheduling, we demonstrate the type of questions a user can have about (non)-solutions, as well as reviewing what kind of computational tools are available today to answer such questions. 19 | We cover classical methods such as MUS/MCS extraction, and more recent advances in the field such as step-wise explanations, constraint relaxation methods, and counterfactual solutions. 20 | We demonstrate and give special attention to techniques that we have successfully (re-)implemented on top of the CPMpy constraint-solving library, which can be readily used today. 21 | 22 | The following presentations are available: 23 | 24 | * ACP 2024 Winter School lecture: [Notebook](https://github.com/CPMpy/XCP-explain/blob/main/acp24-sumschool-xcp.ipynb) [PDF slides](https://github.com/CPMpy/XCP-explain/blob/main/acp24-sumschool-xcp.slides.pdf) [YouTube video](https://youtu.be/nGr4lbgRvzw) 25 | * CP 2023 tutorial: [Notebook](https://github.com/CPMpy/XCP-explain/blob/main/hands-on-tutorial.ipynb) [HTML slides](https://raw.githack.com/CPMpy/CP23-tutorial/main/hands-on-tutorial.slides.html#/1) [PDF slides](https://github.com/CPMpy/XCP-explain/blob/main/hands-on-tutorial%20slides.pdf) [YouTube video](https://www.youtube.com/watch?v=V9DPHZq0gXk) 26 | The slide-show can be viewed from browser by opening the HTML version and using `Space` to go to the next slide. 27 | 28 | Here is the tutorial video for convenience: 29 | [![YouTube video](img/tutorial_thumbnail.png)](https://www.youtube.com/watch?v=V9DPHZq0gXk) 30 | 31 | ## How to run the notebooks? 32 | 33 | To run the `.ipynb` yourself, makes sure you install the following packages: 34 | - CPMpy (>= v0.9.17) 35 | - jupyter 36 | - rise (to make the slideshow) 37 | - faker (to create fake names for nurses) 38 | - pandas (for visualizations) 39 | - matplotlib (for visualizations) 40 | 41 | A one-liner to install pip-packages: 42 | 43 | ```bash 44 | pip install -r requirements.txt 45 | ``` 46 | 47 | Optionally, you can install the `Gurobi` MIP solver for better performance of algorithms relying on incremental solving: 48 | Note that for Gurobi, you will need a license in order to make full use of its power. 49 | 50 | ```bash 51 | pip install gurobipy 52 | ``` 53 | 54 | ## Structure of the repository 55 | ```bash 56 | . 57 | ├── Benchmarks # Nurse scheduling instances 58 | ├── explanations 59 | │   ├── __init__.py 60 | │   ├── counterfactual.py # Counterfactual explanations [1] 61 | │   ├── marco_mcs_mus.py # MARCO enumeration algorithm [2] 62 | │   ├── stepwise # Fork of the step-wise explanations repo [3] 63 | │   └── subset.py # Code to find all kinds of subsets of constraints 64 | ├── factory.py # Wrapper for nsp 65 | ├── hands-on-tutorial slides.pdf # Exectued version of the slides 66 | ├── hands-on-tutorial.ipynb # Runnable version of the slides 67 | ├── hands-on-tutorial.slides.html # .html version of the executed slides 68 | ├── img # Images used in the tutorial 69 | ├── read_data.py # Helper functions to read and wrangle NSP data 70 | └── visualize.py # Helper functions for visualization of constraints and solutions 71 | ``` 72 | 73 | ## How to cite? 74 | ```bibtex 75 | @software{bleukx2024tutorial, 76 | author = {Bleukx, Ignace and 77 | Tsouros, Dimos and 78 | Guns, Tias}, 79 | title = {Explainable Constraint Solving: A hands-on 80 | tutorial 81 | }, 82 | month = feb, 83 | year = 2024, 84 | publisher = {Zenodo}, 85 | version = {v1.0}, 86 | doi = {10.5281/zenodo.10694140}, 87 | url = {https://doi.org/10.5281/zenodo.10694140}, 88 | } 89 | ``` 90 | 91 | ## References 92 | 93 | > [1] Korikov, A., & Beck, J. C. (2021). Counterfactual explanations via inverse constraint programming. In 27th International Conference on Principles and Practice of Constraint Programming (CP 2021). Schloss Dagstuhl-Leibniz-Zentrum für Informatik. 94 | 95 | > [2] Liffiton, M.H., & Malik, A. (2013). Enumerating infeasibility: Finding multiple MUSes quickly. In Proceedings of the 10th International Conference on Integration of AI and OR Techniques in Constraint Programming (CPAIOR 2013) (pp. 160–175) 96 | 97 | > [3] Bleukx, I., Devriendt, J., Gamba, E., Bogaerts B., & Guns T. (2023). Simplifying Step-wise Explanation Sequences. In International Conference on Principles and Practice of Constraint Programming 2023 98 | -------------------------------------------------------------------------------- /acp24-sumschool-xcp.slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/acp24-sumschool-xcp.slides.pdf -------------------------------------------------------------------------------- /ecai2024_practice_part1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "671a1871", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "# install necessary packages (works on Google Colab even if you see a versioning error)\n", 11 | "root = \"/tmp/XCP-explain\"\n", 12 | "! git clone https://github.com/CPMpy/XCP-explain.git {root}\n", 13 | "! cd {root}\n", 14 | "print(\"Installing packages (this can take a minute on Google Colab)\")\n", 15 | "! pip install -r /tmp/XCP-explain/requirements.txt -qq # -qq=very quiet\n", 16 | "\n", 17 | "import sys\n", 18 | "if root not in sys.path:\n", 19 | " sys.path.insert(0, root)\n", 20 | "print(\"Ready to go! (you can ignore Google Colab's google*/tensor* versioning errors)\")" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "f5f7973c-df39-4f8c-9dc2-cb590cee5dc2", 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "# load necessary packages\n", 31 | "import networkx as nx\n", 32 | "import cpmpy as cp\n", 33 | "import cpmpy.tools.explain as cpx\n", 34 | "\n", 35 | "# graph drawing functions\n", 36 | "draw = lambda g,**kwargs : nx.draw(g, nx.spring_layout(g, seed=42), width=4, node_size=500, **kwargs)\n", 37 | "cmap = [\"black\", \"yellow\", \"cyan\", \"lightgreen\", \"blue\", \"red\", \"magenta\", \"orange\", \"purple\", \"brown\"]\n", 38 | "\n", 39 | "import re\n", 40 | "def graph_highlight(graph, cons, dotted=False, **kwargs):\n", 41 | " edges = []\n", 42 | " for c in cons:\n", 43 | " n1, n2 = c.args\n", 44 | " if n1.name == \"max\": continue\n", 45 | " a = int(re.search(\"\\[[0-9]*\\]\", str(n1)).group()[1:-1])\n", 46 | " b = int(re.search(\"\\[[0-9]*\\]\", str(n2)).group()[1:-1])\n", 47 | " edges.append((a,b))\n", 48 | " \n", 49 | " colors = [\"red\" if (a,b) in edges else \"black\" for (a,b) in graph.edges()]\n", 50 | " linestyles = [\"dotted\" if c == \"red\" and dotted else \"solid\" for c in colors]\n", 51 | " return draw(graph, edge_color=colors, style=linestyles, **kwargs)" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "id": "de88a917-f809-453c-8a99-9456d8712ef6", 57 | "metadata": {}, 58 | "source": [ 59 | "## Let's generate a graph\n", 60 | "\n", 61 | "Play with the parameters to get a different one!" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "id": "b6c21406-5813-4bac-9108-d9c2d527e208", 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "G = nx.fast_gnp_random_graph(9, 0.5, seed=21)\n", 72 | "draw(G)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "id": "b4d66ca7-c3c0-4392-b6b0-e789ddaf24ce", 78 | "metadata": {}, 79 | "source": [ 80 | "## How many colors do you think it needs?" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": null, 86 | "id": "357fc8d4-68b6-460c-af52-62b333a9f9af", 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "max_colors = 3 # YOUR GUESS FOR YOUR GRAPH\n", 91 | "\n", 92 | "def graph_color_k(G, max_colors):\n", 93 | " # number of nodes\n", 94 | " n = G.number_of_nodes()\n", 95 | "\n", 96 | " # decision variables, one for every node\n", 97 | " x = cp.intvar(1, max_colors, shape=n, name=\"x\")\n", 98 | "\n", 99 | " # constraints: neighbors have different colors\n", 100 | " m = cp.Model(\n", 101 | " [x[i] != x[j] for i,j in G.edges()],\n", 102 | " )\n", 103 | " return m, x\n", 104 | "\n", 105 | "m, nodes = graph_color_k(G, max_colors)\n", 106 | "if m.solve():\n", 107 | " print(m.status())\n", 108 | " print(f\"Yes! There is a coloring with {max(nodes.value())} <= {max_colors} colors:\")\n", 109 | " draw(G, node_color=[cmap[v.value()] for v in nodes])\n", 110 | "else:\n", 111 | " print(\"No solution found, let the expaining begin : )\")" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "id": "49a74187-1344-46d7-88c7-a08424f2da53", 117 | "metadata": {}, 118 | "source": [ 119 | "## Given too few colors, lets find a deductive explanation: what causes the UNSAT?" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "id": "c005d719-340d-409b-84e8-00046c40db53", 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [ 129 | "m, nodes = graph_color_k(G, max_colors) # You can change the colors here\n", 130 | "assert not m.solve(), \"Choose too few colors to continue\"\n", 131 | "\n", 132 | "conflict = cpx.mus(m.constraints) # Minimal Unsatisfiable Subset\n", 133 | "print(f\"UNSAT with {max_colors} colors is caused by the following minimal constraint set:\")\n", 134 | "print(conflict)\n", 135 | "graph_highlight(G, conflict)" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "id": "9a5d210f-2adf-410e-97e1-ea2d74d8c73d", 141 | "metadata": {}, 142 | "source": [ 143 | "## Run it multiple times and it might find different conflicts..." 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": null, 149 | "id": "6b72bc8f-35ec-4be4-a88d-640db72c07d4", 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "for i in range(50):\n", 154 | " conflict2 = cpx.mus(m.constraints) # Minimal Unsatisfiable Subset\n", 155 | " if conflict2 != conflict:\n", 156 | " break # found different conflict\n", 157 | "\n", 158 | "print(f\"UNSAT with {max_colors} colors is caused by the following minimal constraint set:\")\n", 159 | "print(conflict2)\n", 160 | "graph_highlight(G, conflict2)" 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "id": "e72bb877-9e15-48ef-a951-d6f53d0e5430", 166 | "metadata": {}, 167 | "source": [ 168 | "You might be surprised by the size of the of the MUSes, but we assure you these are subset minimal!\n", 169 | "\n", 170 | "## OK, lets find a counterfactual explanation: what has to change to make it SAT?" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": null, 176 | "id": "7e83cff7-45e7-468f-aa20-b3cadd350b97", 177 | "metadata": {}, 178 | "outputs": [], 179 | "source": [ 180 | "m, nodes = graph_color_k(G, max_colors) # You can change the colors here\n", 181 | "assert not m.solve(), \"Choose too few colors to continue\"\n", 182 | "\n", 183 | "correction = cpx.mcs(m.constraints) # Minimal Correction Subset\n", 184 | "print(f\"UNSAT with {max_colors} colors can be resolved by removing the following minimal constraint set:\")\n", 185 | "print(correction)\n", 186 | "# compute and visualise counter-factual solution\n", 187 | "cp.Model([c for c in m.constraints if c not in correction]).solve()\n", 188 | "graph_highlight(G, correction, node_color=[cmap[n.value()] for n in nodes], dotted=True)\n", 189 | "print(\"\\n(removed constraints are highlighted in dotted red)\")" 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "id": "93ca9d46-2eb7-479d-b892-bab9da6d4f4a", 195 | "metadata": {}, 196 | "source": [ 197 | "## There are typically also many MCS's..." 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": null, 203 | "id": "277f2ec6-1a30-4724-a7e8-9e630e94fe9a", 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "for i in range(50):\n", 208 | " correction2 = cpx.mcs(m.constraints) # Minimal Correction Subset\n", 209 | " if correction2 != correction:\n", 210 | " break # found different conflict\n", 211 | "\n", 212 | "print(f\"UNSAT with {max_colors} colors can be resolved by removing the following minimal constraint set:\")\n", 213 | "print(correction2)\n", 214 | "# compute and visualise counter-factual solution\n", 215 | "cp.Model([c for c in m.constraints if c not in correction2]).solve()\n", 216 | "graph_highlight(G, correction2, node_color=[cmap[n.value()] for n in nodes], dotted=True)\n", 217 | "print(\"\\n(removed constraints are highlighted in dotted red)\")" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": null, 223 | "id": "0f096949-9106-42f7-bbf6-7317a1b0f7f0", 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "id": "307029fd-beb0-4e04-91ee-386521158e95", 231 | "metadata": {}, 232 | "source": [ 233 | "## Higher-level constraints\n", 234 | "\n", 235 | "In the graph coloring example each constraint is one atomic inequalty. The same techniques also work on 'complex' constraints, that is, a high-level constraint that when translated to a solver corresponds to a *group* of constraints.\n", 236 | "\n", 237 | "### Here is an example: the Photo alignment problem.\n", 238 | "\n", 239 | "Imagine: You're freshly graduated and starting your first job as a CS teacher at a local highschool. It's early in the morning and the students are coming in dressed in their finest of clothes. It's that time of the year again: the class photo! You're in charge of lining up the students for the class photo. Many of the students want to stand next to their friends and refuse to go on the photo otherwise. As a new teacher, you want to make sure that everyone is happy.\n" 240 | ] 241 | }, 242 | { 243 | "cell_type": "code", 244 | "execution_count": null, 245 | "id": "0fddd670-099d-422c-bd3e-3fc60a0ae8a8", 246 | "metadata": {}, 247 | "outputs": [], 248 | "source": [ 249 | "wish_yes = [(\"Tias\", \"Dimos\"), (\"Tias\", \"Ignace\"), (\"Dimos\", \"Stella\"), (\"Ignace\", \"Helene\"), (\"Helene\", \"Dimos\"), (\"Stella\", \"Tias\"), (\"Ignace\", \"Thomas\"), (\"Lucifer\", \"Dimon\")]\n", 250 | "\n", 251 | "wish_no = [(\"Tias\", \"Lucifer\"), (\"Dimos\", \"Dimon\"), (\"Stella\", \"Lucifer\")]\n", 252 | "\n", 253 | "people = sorted(set(n for pair in wish_yes+wish_no for n in pair))\n", 254 | "dmap = {name: index for index,name in enumerate(people)} # name -> index\n", 255 | "print(people)\n", 256 | "\n", 257 | "position = cp.intvar(0,len(people)-1, shape=len(people), name=people)\n", 258 | "\n", 259 | "# HARD constraint: everybody a unique position\n", 260 | "con_diff = cp.alldifferent(position)\n", 261 | "\n", 262 | "# Yes wishes must have difference in position of 1\n", 263 | "cons_yes = []\n", 264 | "for (a, b) in wish_yes:\n", 265 | " cons_yes.append( cp.abs(position[dmap[a]] - position[dmap[b]]) == 1 )\n", 266 | "# No wishes can not have difference in position of 1\n", 267 | "cons_no = []\n", 268 | "for (a, b) in wish_no:\n", 269 | " cons_no.append( cp.abs(position[dmap[a]] - position[dmap[b]]) > 1 )\n", 270 | "\n", 271 | "m = cp.Model(con_diff, cons_yes, cons_no)\n", 272 | "print(m)\n", 273 | "m.solve()" 274 | ] 275 | }, 276 | { 277 | "cell_type": "markdown", 278 | "id": "8bb3ab92-0e51-481e-94de-fe06af74750a", 279 | "metadata": {}, 280 | "source": [ 281 | "## It's unsat... try to find a MUS/MCS...\n", 282 | "\n", 283 | "In this case, we will set the 'con_diff' as a HARD constraint.\n", 284 | "\n", 285 | "So the MUS/MCS will only consider the cons_yes/cons_no" 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": null, 291 | "id": "e5e85ab9-fd45-4ce3-a5f5-2c3ae028dc45", 292 | "metadata": {}, 293 | "outputs": [], 294 | "source": [ 295 | "#conflict = cpx.mus(cons_yes+cons_no, hard=[con_diff]) # Minimal Unsatisfiable Subset\n", 296 | "#print(conflict)" 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": null, 302 | "id": "355cb7bb-c42a-4adc-bc0e-e691ec50d734", 303 | "metadata": {}, 304 | "outputs": [], 305 | "source": [ 306 | "correction = []\n", 307 | "#correction = cpx.mcs(cons_yes+cons_no, hard=[con_diff]) # Minimal Correction Subset\n", 308 | "#print(correction)" 309 | ] 310 | }, 311 | { 312 | "cell_type": "code", 313 | "execution_count": null, 314 | "id": "883478dc-383f-47d2-bdfb-9f852a1af2d1", 315 | "metadata": {}, 316 | "outputs": [], 317 | "source": [ 318 | "m2 = cp.Model(con_diff, [c for c in cons_yes+cons_no if hash(c) not in [hash(c) for c in correction]])\n", 319 | "#print(m2)\n", 320 | "if not m2.solve():\n", 321 | " print(\"No solution, try removing some constraints\")\n", 322 | "else:\n", 323 | " # print in the right order\n", 324 | " sol = [(pos.value(), str(pos)) for pos in position]\n", 325 | " print(sorted(sol))" 326 | ] 327 | } 328 | ], 329 | "metadata": { 330 | "kernelspec": { 331 | "display_name": "Python 3 (ipykernel)", 332 | "language": "python", 333 | "name": "python3" 334 | }, 335 | "language_info": { 336 | "codemirror_mode": { 337 | "name": "ipython", 338 | "version": 3 339 | }, 340 | "file_extension": ".py", 341 | "mimetype": "text/x-python", 342 | "name": "python", 343 | "nbconvert_exporter": "python", 344 | "pygments_lexer": "ipython3", 345 | "version": "3.11.3" 346 | } 347 | }, 348 | "nbformat": 4, 349 | "nbformat_minor": 5 350 | } 351 | -------------------------------------------------------------------------------- /ecai2024_practice_part2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "# code required to run on a fresh install or in google colab\n", 10 | "root = \"/tmp/XCP-explain\"\n", 11 | "! git clone https://github.com/CPMpy/XCP-explain.git {root}\n", 12 | "! cd {root}\n", 13 | "! pip install -r {root}/requirements.txt\n", 14 | "! pip install cpmpy\n", 15 | "\n", 16 | "# add XCP-explain to the Python path\n", 17 | "import sys\n", 18 | "if root not in sys.path:\n", 19 | " sys.path.insert(0, root)" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "# Hands on deductive explanations\n", 27 | "\n", 28 | "In this notebook, we will use another instance of the nurse rostering problem to generate some deductive explanations." 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "First, let's inspect the instance" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "metadata": { 42 | "pycharm": { 43 | "name": "#%%\n" 44 | }, 45 | "slideshow": { 46 | "slide_type": "-" 47 | } 48 | }, 49 | "outputs": [], 50 | "source": [ 51 | "\"\"\"\n", 52 | " Some imports used throughout the notebook\n", 53 | "\"\"\"\n", 54 | "import os\n", 55 | "import time\n", 56 | "from visualize import *\n", 57 | "\n", 58 | "# functions required for generating the model\n", 59 | "from read_data import get_data" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "from read_data import get_data\n", 69 | "from factory import *\n", 70 | "\n", 71 | "instance = os.path.join(root,\"Benchmarks/CustomInstance.txt\")\n", 72 | "data = get_data(instance)\n", 73 | "factory = NurseSchedulingFactory(data)" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "metadata": { 80 | "pycharm": { 81 | "name": "#%%\n" 82 | }, 83 | "scrolled": false, 84 | "slideshow": { 85 | "slide_type": "-" 86 | } 87 | }, 88 | "outputs": [], 89 | "source": [ 90 | "data.staff[[\"name\", \"MaxShifts\",\"MaxWeekends\"]]" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "print(f\"Planning for {data.horizon} days\")" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "Now, let's solve the model.\n", 107 | "\n", 108 | "In the optimization formulation as given by schedulingbenchmarks.org, some constraints or requests may be unsatisfied." 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": null, 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [ 117 | "model, nurse_view = factory.get_optimization_model()\n", 118 | "assert model.solve(solver=\"ortools\", num_workers=8) # need 8 workers for efficient solving\n", 119 | "\n", 120 | "print(model.status())\n", 121 | "print(\"Total penalty:\", model.objective_value())\n", 122 | "visualize(nurse_view.value(), factory)" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "which requests are not satisfied by this solution?" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": null, 135 | "metadata": {}, 136 | "outputs": [], 137 | "source": [ 138 | "requests, _ = factory.shift_on_requests(formulation=\"hard\")\n", 139 | "\n", 140 | "denied_requests = [req for req in requests if req.value() is False]\n", 141 | "print(\"The following requests were denied:\")\n", 142 | "for req in denied_requests:\n", 143 | " print(\"-\", req)\n", 144 | "\n", 145 | "visualize_constraints(denied_requests, nurse_view, factory, do_clear=False)" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "# try it yourself!\n", 155 | "\n", 156 | "# requests, _ = factory.shift_off_requests(formulation=\"hard\")\n", 157 | "# cover_constraints, _ = factory.cover(formulation=\"hard\")\n", 158 | "\n", 159 | "# TODO: find out which are not satisfied, and visualize!" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "metadata": {}, 165 | "source": [ 166 | "Ok, so clearly it's not possible to satisfy all constraints and requests.\n", 167 | "\n", 168 | "But _why_ is that the case? Can we gain more insight in this instance and investigate how the conflic(t)s look like?\n", 169 | "\n", 170 | "\n" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "metadata": {}, 176 | "source": [ 177 | "We treat all constraints and requests equal, so we get a _decision_ problem" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": null, 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [ 186 | "from cpmpy.tools.explain import mus\n", 187 | "\n", 188 | "model, nurse_view = factory.get_decision_model()\n", 189 | "\n", 190 | "subset = mus(model.constraints)\n", 191 | "for c in subset:\n", 192 | " print(\"-\", c, c.__repr__())\n", 193 | "visualize_constraints(subset, nurse_view, factory)" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "Are there more MUSes? Of course :-)\n", 201 | "\n", 202 | "Let's enumerate a few of them using the MARCO algorithm" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": null, 208 | "metadata": { 209 | "scrolled": false 210 | }, 211 | "outputs": [], 212 | "source": [ 213 | "from cpmpy.tools.explain import marco\n", 214 | "\n", 215 | "model, nurse_view = factory.get_decision_model()\n", 216 | "\n", 217 | "for i, (kind, subset) in enumerate(marco(model.constraints, solver=\"exact\", return_mcs=False)):\n", 218 | " if kind == \"MUS\":\n", 219 | " display(visualize_constraints(subset, nurse_view, factory))\n", 220 | " \n", 221 | " if i == 3:\n", 222 | " break" 223 | ] 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "metadata": {}, 228 | "source": [ 229 | "Clearly, these MUSes are very different! And some are more interpretable than others.\n", 230 | "\n", 231 | "In the remainder of this notebook, we will influence which MUS is found.\n", 232 | "\n", 233 | "First, by finding prefered MUSes using QuickXplain, then finding optimal MUSes given a cost function" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "# QuickXplain first\n", 243 | "from cpmpy.tools.explain import quickxplain\n", 244 | "\n", 245 | "model, nurse_view = factory.get_decision_model()\n", 246 | "\n", 247 | "# DIY: craft your own ordering of constraints here!\n", 248 | "def get_order(cons):\n", 249 | " if \"cover\" in str(cons): # Find a MUS that does include many cover constraints\n", 250 | " return 1\n", 251 | " return 10\n", 252 | "\n", 253 | "\n", 254 | "ordered = sorted(model.constraints, key=get_order)\n", 255 | "conflict = quickxplain(ordered)\n", 256 | "for c in conflict:\n", 257 | " print(\"-\", c)" 258 | ] 259 | }, 260 | { 261 | "cell_type": "code", 262 | "execution_count": null, 263 | "metadata": {}, 264 | "outputs": [], 265 | "source": [ 266 | "visualize_constraints(conflict, nurse_view, factory)" 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "metadata": {}, 272 | "source": [ 273 | "#### Optimal MUS\n", 274 | "\n", 275 | "Now find truely OPTIMAL MUSes given a cost function $f$\n" 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": null, 281 | "metadata": {}, 282 | "outputs": [], 283 | "source": [ 284 | "## Careful, this takes a while if you are not using Exact!\n", 285 | "from cpmpy.tools.explain import optimal_mus\n", 286 | "\n", 287 | "model, nurse_view = factory.get_decision_model()\n", 288 | "\n", 289 | "# DIY: craft your own cost for constraints here!\n", 290 | "def get_weight(cons):\n", 291 | " return 1 # find the smallest one\n", 292 | "\n", 293 | "solver = \"exact\" if \"exact\" in cp.SolverLookup.solvernames() else \"ortools\"\n", 294 | "hs_solver = \"gurobi\" if \"gurobi\" in cp.SolverLookup.solvernames() else \"ortools\"\n", 295 | "\n", 296 | "conflict = optimal_mus(model.constraints, \n", 297 | " weights=[get_weight(c) for c in model.constraints],\n", 298 | " solver=solver,\n", 299 | " hs_solver=hs_solver)\n", 300 | "print(f\"Found conflict of size {len(conflict)}\")" 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": null, 306 | "metadata": {}, 307 | "outputs": [], 308 | "source": [ 309 | "visualize_constraints(conflict, nurse_view, factory)" 310 | ] 311 | }, 312 | { 313 | "cell_type": "markdown", 314 | "metadata": {}, 315 | "source": [ 316 | "# Part 2, fixing UNSAT models\n", 317 | "\n", 318 | "Now that we know _why_ a model is UNSAT, we need to fix it.\n", 319 | "\n", 320 | "In the presentation, several techniques are shown for doing so.\n", 321 | "\n", 322 | "Below, you can find some skeleton code to play around with feasibiliy restoration techniques" 323 | ] 324 | }, 325 | { 326 | "cell_type": "code", 327 | "execution_count": null, 328 | "metadata": {}, 329 | "outputs": [], 330 | "source": [ 331 | "model, nurse_view = factory.get_decision_model()\n", 332 | "model.solve()" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": null, 338 | "metadata": {}, 339 | "outputs": [], 340 | "source": [ 341 | "from cpmpy.tools.explain import mss_opt, mcs_opt\n", 342 | "\n", 343 | "# DIY: craft your own cost for constraints here!\n", 344 | "def get_weight(cons):\n", 345 | " return 1 # equal weights\n", 346 | "\n", 347 | "# find Max-CSP solution\n", 348 | "optimal_subset = mss_opt(model.constraints, hard=[],weights=[get_weight(c) for c in model.constraints])\n", 349 | "mcs = set(model.constraints) - set(optimal_subset)\n", 350 | "print(\"Found solution after dropping these constraints:\")\n", 351 | "for i,c in enumerate(mcs):\n", 352 | " print(f\"{i}.\", c)\n" 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": null, 358 | "metadata": {}, 359 | "outputs": [], 360 | "source": [ 361 | "assert cp.Model(optimal_subset).solve() is True\n", 362 | "visualize(nurse_view.value(), factory)\n", 363 | "visualize_constraints(mcs, nurse_view, factory, do_clear=False)" 364 | ] 365 | }, 366 | { 367 | "cell_type": "markdown", 368 | "metadata": {}, 369 | "source": [ 370 | "### Slack-based relaxation\n", 371 | "\n", 372 | "Apart from dropping constraints, they can also be _relaxed_ when numeric" 373 | ] 374 | }, 375 | { 376 | "cell_type": "code", 377 | "execution_count": null, 378 | "metadata": {}, 379 | "outputs": [], 380 | "source": [ 381 | "model, nurse_view, slack_under, slack_over = factory.get_slack_model() # CMPpy Model\n", 382 | "\n", 383 | "# minimize the maximal violation\n", 384 | "slack = cp.cpm_array(np.append(slack_under, slack_over))\n", 385 | "model.minimize(cp.max(slack))\n", 386 | "\n", 387 | "assert model.solve()\n", 388 | "\n", 389 | "visualize(nurse_view.value(), factory)" 390 | ] 391 | }, 392 | { 393 | "cell_type": "code", 394 | "execution_count": null, 395 | "metadata": {}, 396 | "outputs": [], 397 | "source": [ 398 | "# DIY: craft your own objective functions" 399 | ] 400 | } 401 | ], 402 | "metadata": { 403 | "celltoolbar": "Slideshow", 404 | "kernelspec": { 405 | "display_name": "Python 3 (ipykernel)", 406 | "language": "python", 407 | "name": "python3" 408 | }, 409 | "language_info": { 410 | "codemirror_mode": { 411 | "name": "ipython", 412 | "version": 3 413 | }, 414 | "file_extension": ".py", 415 | "mimetype": "text/x-python", 416 | "name": "python", 417 | "nbconvert_exporter": "python", 418 | "pygments_lexer": "ipython3", 419 | "version": "3.10.14" 420 | }, 421 | "rise": { 422 | "transition": "none" 423 | }, 424 | "varInspector": { 425 | "cols": { 426 | "lenName": 16, 427 | "lenType": 16, 428 | "lenVar": 40 429 | }, 430 | "kernels_config": { 431 | "python": { 432 | "delete_cmd_postfix": "", 433 | "delete_cmd_prefix": "del ", 434 | "library": "var_list.py", 435 | "varRefreshCmd": "print(var_dic_list())" 436 | }, 437 | "r": { 438 | "delete_cmd_postfix": ") ", 439 | "delete_cmd_prefix": "rm(", 440 | "library": "var_list.r", 441 | "varRefreshCmd": "cat(var_dic_list()) " 442 | } 443 | }, 444 | "types_to_exclude": [ 445 | "module", 446 | "function", 447 | "builtin_function_or_method", 448 | "instance", 449 | "_Feature" 450 | ], 451 | "window_display": false 452 | } 453 | }, 454 | "nbformat": 4, 455 | "nbformat_minor": 4 456 | } 457 | -------------------------------------------------------------------------------- /ecai2024_presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/ecai2024_presentation.pdf -------------------------------------------------------------------------------- /explanations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/explanations/__init__.py -------------------------------------------------------------------------------- /explanations/counterfactual.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import cpmpy as cp 4 | from cpmpy.transformations.get_variables import get_variables 5 | from cpmpy.expressions.core import Expression 6 | 7 | INFTY = 1000 8 | 9 | def inverse_optimize(model:cp.Model, user_sol:dict, allowed_to_change:set, minimize=True): 10 | 11 | sub_problem = model.copy() 12 | 13 | obj = model.objective_ 14 | assert isinstance(obj, Expression) and obj.name == "wsum" 15 | obj_weights, obj_vars = obj.args 16 | obj_vars = cp.cpm_array(obj_vars) 17 | wvars = cp.intvar(-INFTY, INFTY, shape=len(obj_weights)) 18 | 19 | master_problem = cp.Model() 20 | for wvar,w,v in zip(wvars, obj_weights, obj_vars): 21 | if v not in allowed_to_change: 22 | master_problem += wvar == w 23 | 24 | if len(user_sol) != len(obj_vars): 25 | # neirest counterfactual explanation case, complete to full assignment of the objective vars 26 | um = cp.Model(model.constraints + [var == val for var, val in user_sol.items()]) 27 | um.objective(model.objective_, minimize=minimize) 28 | assert um.solve() 29 | user_sol = {var : var.value() for var in obj_vars} 30 | 31 | # covert dict to array in correct order 32 | user_arr = [0 for _ in obj_vars] 33 | for key, val in user_sol.items(): 34 | idx = next(i for i, var in enumerate(obj_vars) if str(var) == str(key)) 35 | user_arr[idx] = val 36 | 37 | 38 | diff = wvars - obj_weights 39 | master_problem.minimize(np.linalg.norm(diff,ord=1)) 40 | 41 | while 1: 42 | assert master_problem.solve() # find minimal perturbation in coefficients 43 | new_weights = wvars.value() 44 | sub_problem.objective(cp.sum(new_weights * obj_vars), minimize=minimize) 45 | assert sub_problem.solve() 46 | 47 | user_objval = (new_weights * user_arr).sum() 48 | 49 | if minimize: 50 | # minimization problem, check if obj val of user is lowest possible with these weights 51 | if user_objval <= sub_problem.objective_value(): 52 | return cp.sum(new_weights * obj_vars) 53 | else: 54 | master_problem += cp.sum(wvars * user_arr) <= cp.sum(wvars * obj_vars.value()) 55 | 56 | 57 | else: 58 | # maximization problem, check if obj val of user is highest possible with these weights 59 | if user_objval >= sub_problem.objective_value(): 60 | return cp.sum(new_weights * obj_vars) 61 | else: 62 | master_problem += cp.sum(wvars * user_arr) >= cp.sum(wvars * obj_vars.value()) 63 | 64 | 65 | if __name__ == "__main__": 66 | import cpmpy as cp 67 | 68 | bvars = cp.boolvar(shape=8) 69 | values = [5, 0, 3, 3, 7, 9, 3, 5] 70 | weights = [2, 4, 7, 6, 8, 8, 1, 6] 71 | 72 | m = cp.Model(cp.sum(bvars * weights) <= 35) 73 | m.maximize(cp.sum(bvars * values)) 74 | 75 | user_sol = {bvars[1] : True, bvars[2] : True} 76 | 77 | allowed_changes = set([bvars[1], bvars[2]]) 78 | 79 | print(inverse_optimize(m, user_sol, allowed_changes, minimize=False)) 80 | 81 | 82 | -------------------------------------------------------------------------------- /explanations/diagnosis.py: -------------------------------------------------------------------------------- 1 | 2 | import cpmpy as cp 3 | from cpmpy.tools.explain.utils import make_assump_model 4 | from cpmpy.transformations.get_variables import get_variables 5 | 6 | def diagnose(soft, hard=[], solver="ortools", callback=lambda x : None): 7 | 8 | model, soft, assump = make_assump_model(soft, hard) 9 | dmap = dict(zip(assump, soft)) 10 | s = cp.SolverLookup.get(solver, model) 11 | 12 | sat_subset = set(assump) 13 | corr_subset = [] 14 | 15 | while s.solve(assumptions=list(sat_subset)) is False: 16 | 17 | # find new core 18 | core = set(s.get_core()) 19 | 20 | for c in sorted(core, key= lambda a : len(get_variables(dmap[a]))): 21 | if c not in core: 22 | continue # already removed 23 | core.remove(c) 24 | if s.solve(assumptions=core) is True: 25 | # need constraint 26 | core.add(c) 27 | else: # UNSAT, do clause set refinement 28 | core = set(s.get_core()) 29 | 30 | core = sorted(core, key=lambda a : str(dmap[a])) 31 | mus = [dmap[a] for a in core] 32 | callback(mus) 33 | print("Constraints in conflict:") 34 | for i, c in enumerate(mus): 35 | print(f"{i}.", c) 36 | 37 | print("and already removed constraints:") 38 | for a in corr_subset: 39 | print("-", dmap[a]) 40 | 41 | user_input = input("Chose a constraint to remove (-1 for exit):") 42 | while len(user_input) <= 0: 43 | user_input = input("Chose a constraint to remove (-1 for exit):") 44 | idx = int(user_input) 45 | if idx < 0: break 46 | 47 | sat_subset.remove(core[idx]) 48 | corr_subset.append(core[idx]) 49 | 50 | return [dmap[a] for a in corr_subset] 51 | 52 | 53 | 54 | def diagnose_optimal(soft, hard=[], weights=None, solver="ortools", hs_solver="ortools", callback=lambda x : None): 55 | 56 | model, soft, assump = make_assump_model(soft, hard) 57 | dmap = dict(zip(assump, soft)) 58 | s = cp.SolverLookup.get(solver, model) 59 | 60 | 61 | if weights is None: 62 | weights = [1] * len(soft) 63 | hs_solver = cp.SolverLookup.get(hs_solver) 64 | hs_solver.minimize(cp.sum(weights * assump)) 65 | 66 | sat_subset = set(assump) 67 | corr_subset = [] 68 | 69 | while s.solve(assumptions=list(sat_subset)) is False: 70 | 71 | # find optimal MUS with OCUS 72 | while hs_solver.solve(): 73 | 74 | hitting_set = [a for a in assump if a.value()] 75 | if s.solve(assumptions=hitting_set) is False: 76 | break # found UNSAT 77 | 78 | # else, the hitting set is SAT, now try to extend it without extra solve calls. 79 | # Check which other assumptions/constraints are satisfied (using c.value()) 80 | # complement of grown subset is a correction subset 81 | new_corr_subset = [a for a, c in zip(assump, soft) if a.value() is False and c.value() is False] 82 | hs_solver += cp.sum(new_corr_subset) >= 1 83 | 84 | # greedily search for other corr subsets disjoint to this one 85 | sat_subset = list(new_corr_subset) 86 | while s.solve(assumptions=sat_subset) is True: 87 | new_corr_subset = [a for a, c in zip(assump, soft) if a.value() is False and c.value() is False] 88 | sat_subset += new_corr_subset # extend sat subset with new corr subset, guaranteed to be disjoint 89 | hs_solver += cp.sum(new_corr_subset) >= 1 # add new corr subset to hitting set solver 90 | 91 | # hitting set is UNSAT, so found optimal MUS 92 | core = sorted(hitting_set, key=lambda a : str(dmap[a])) 93 | mus = [dmap[a] for a in core] 94 | callback(mus) 95 | print("Constraints in conflict:") 96 | for i, c in enumerate(mus): 97 | print(f"{i}.", c) 98 | 99 | print("and already removed constraints:") 100 | for a in corr_subset: 101 | print("-", dmap[a]) 102 | 103 | user_input = input("Chose a constraint to remove (-1 for exit):") 104 | while len(user_input) <= 0: 105 | user_input = input("Chose a constraint to remove (-1 for exit):") 106 | idx = int(user_input) 107 | if idx < 0: break 108 | 109 | sat_subset.remove(core[idx]) 110 | hs_solver += ~core[idx] # disable for future MUSes 111 | corr_subset.append(core[idx]) 112 | 113 | return [dmap[a] for a in corr_subset] 114 | 115 | 116 | -------------------------------------------------------------------------------- /explanations/marco_mcs_mus.py: -------------------------------------------------------------------------------- 1 | """ 2 | MARCO-style, but only for MSSes 3 | and knowing that ORTools can do maxsat(=grow) calls natively 4 | 5 | so: get a maxsat, block it down, get another, etc. 6 | blocking is directly on the maxsat model, so single model... 7 | """ 8 | 9 | from cpmpy import * 10 | from cpmpy.transformations.normalize import toplevel_list 11 | 12 | 13 | def do_marco(mdl, solver="ortools"): 14 | """ 15 | Basic MUS/MCS enumeration, as a simple example. 16 | 17 | Warning: all constraints in 'mdl' must support reification! 18 | Otherwise, you will get an "Or-tools says: invalid" error. 19 | """ 20 | # ensure toplevel list 21 | cons = toplevel_list(mdl.constraints, merge_and=False) 22 | 23 | sub_solver = SubsetSolver(cons, solver=solver) 24 | map_solver = MapSolver(len(cons), solver=solver) 25 | 26 | while True: 27 | seed = map_solver.next_seed() 28 | if seed is None: 29 | # all MUS/MSS enumerated 30 | return 31 | 32 | if sub_solver.check_subset(seed): 33 | MSS = sub_solver.grow(seed) 34 | yield ("MSS", [cons[i] for i in MSS]) 35 | map_solver.block_down(MSS) 36 | else: 37 | seed = sub_solver.seed_from_core() 38 | MUS = sub_solver.shrink(seed) 39 | yield ("MUS", [cons[i] for i in MUS]) 40 | map_solver.block_up(MUS) 41 | 42 | 43 | class SubsetSolver: 44 | def __init__(self, constraints, solver=None, warmstart=False): 45 | n = len(constraints) 46 | self.all_n = set(range(n)) # used for complement 47 | 48 | # intialise indicators 49 | self.indicators = BoolVar(shape=n) 50 | self.idcache = dict((v,i) for (i,v) in enumerate(self.indicators)) 51 | # XXX prefer to remove constraints with more variables first 52 | self.idpref = [1]*n #[len(get_variables(constraints[i])) for i in self.all_n] 53 | 54 | # make reified model 55 | mdl_reif = Model([ self.indicators[i].implies(con) for i,con in enumerate(constraints) ]) 56 | self.solver = SolverLookup.get(solver, mdl_reif) 57 | 58 | self.warmstart = warmstart 59 | if warmstart: 60 | # for warmstarting from a previous solution 61 | self.user_vars = self.solver.user_vars 62 | self.user_vars_sol = None 63 | 64 | def check_subset(self, seed): 65 | assump = [self.indicators[i] for i in seed] 66 | if self.warmstart and self.user_vars_sol is not None: 67 | # or-tools is not incremental, 68 | # but we can warmstart with previous solution 69 | self.solver.solution_hint(self.user_vars, self.user_vars_sol) 70 | 71 | ret = self.solver.solve(assumptions=assump) 72 | if self.warmstart and ret is not False: 73 | # store solution for warm start 74 | self.user_vars_sol = [v.value() for v in self.user_vars] 75 | 76 | return ret 77 | 78 | def seed_from_core(self): 79 | core = self.solver.get_core() 80 | return set(self.idcache[v] for v in core) 81 | 82 | def shrink(self, seed): 83 | current = set(seed) # will change during loop 84 | # TODO: there is room for ordering the constraints here 85 | # E.G. by nr of variables involved... 86 | for i in sorted(seed, key=lambda i: self.idpref[i]): 87 | if i not in current: 88 | continue 89 | current.remove(i) 90 | if not self.check_subset(current): 91 | # if UNSAT, shrink to its core 92 | current = self.seed_from_core() 93 | else: 94 | # without 'i' its SAT, so add back 95 | current.add(i) 96 | return current 97 | 98 | def grow(self, seed): 99 | current = seed 100 | for i in (self.all_n).difference(seed): # complement 101 | current.append(i) 102 | if not self.check_subset(current): 103 | # if UNSAT, do not add in grow 104 | current.pop() 105 | return current 106 | 107 | 108 | class MapSolver: 109 | def __init__(self, n, solver=None): 110 | """Initialization. 111 | Args: 112 | n: The number of constraints to map. 113 | """ 114 | self.all_n = set(range(n)) # used for complement 115 | 116 | self.indicators = BoolVar(shape=n) 117 | # default to true for first next_seed(), "high bias" 118 | for v in self.indicators: 119 | v._value = True 120 | 121 | # empty model 122 | self.solver = SolverLookup.get(solver) 123 | 124 | def next_seed(self): 125 | """Get the seed from the current model, if there is one. 126 | Returns: 127 | A seed as an array of 0-based constraint indexes. 128 | """ 129 | # try to select a lot, then it is more likely to be unsat 130 | try: 131 | self.solver.solution_hint(self.indicators, [1]*len(self.indicators)) 132 | except: 133 | pass 134 | if self.solver.solve() is False: 135 | return None 136 | return [i for i,v in enumerate(self.indicators) if v.value()] 137 | 138 | def block_down(self, frompoint): 139 | """Block down from a given set.""" 140 | complement = (self.all_n).difference(frompoint) 141 | self.solver += any(self.indicators[i] for i in complement) 142 | 143 | def block_up(self, frompoint): 144 | """Block up from a given set.""" 145 | self.solver += any(~self.indicators[i] for i in frompoint) 146 | -------------------------------------------------------------------------------- /explanations/stepwise/__init__.py: -------------------------------------------------------------------------------- 1 | from .forward import construct_greedy 2 | from .backward import relax_sequence, filter_sequence 3 | from .datastructures import DomainSet 4 | 5 | from cpmpy.transformations.normalize import toplevel_list 6 | from cpmpy.transformations.get_variables import get_variables 7 | 8 | 9 | def find_sequence(constraints): 10 | 11 | constraints = toplevel_list(constraints, merge_and=False) 12 | unsat = DomainSet({var : frozenset() for var in get_variables(constraints)}) 13 | seq = construct_greedy(constraints, unsat, time_limit=100, seed=0) 14 | print(f"Found sequence of length {len(seq)}") 15 | filtered = filter_sequence(seq, goal_reduction=unsat, time_limit=100) 16 | print(f"Filtered sequence to length {len(filtered)}") 17 | return relax_sequence(filtered, time_limit=100) 18 | 19 | 20 | def forward_construction(constraints): 21 | 22 | constraints = toplevel_list(constraints, merge_and=False) 23 | unsat = DomainSet({var : frozenset() for var in get_variables(constraints)}) 24 | seq = construct_greedy(constraints, unsat, time_limit=100, seed=1) 25 | 26 | return seq 27 | 28 | def backward_filtering(constraints, seq): 29 | 30 | unsat = DomainSet({var : frozenset() for var in get_variables(constraints)}) 31 | filtered = filter_sequence(seq, goal_reduction=unsat, time_limit=100) 32 | return filtered 33 | -------------------------------------------------------------------------------- /explanations/stepwise/backward.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from time import time 3 | import logging 4 | 5 | import cpmpy as cp 6 | from cpmpy.tools.mus import mus 7 | from cpmpy.transformations.get_variables import get_variables 8 | from cpmpy.transformations.normalize import toplevel_list 9 | 10 | from .datastructures import DomainSet, EPSILON 11 | from .propagate import MaximalPropagate, CPPropagate, ExactPropagate, MaximalPropagateSolveAll 12 | from ..subset import smus 13 | 14 | 15 | def filter_sequence(seq, goal_reduction, time_limit, propagator_class=MaximalPropagate): 16 | """ 17 | Filter sequence from redundant steps. 18 | loops over sequence from back to front and attempts to leave out a step 19 | if the remaining sequence is still valid, it is removed, otherwise the step is kept in the sequence 20 | """ 21 | seq = copy.deepcopy(seq) 22 | 23 | start_time = time() 24 | 25 | constraints = set().union(*[set(step.S) for step in seq]) 26 | propagator = propagator_class(list(constraints), caching=True) 27 | cp_propagator = CPPropagate(list(constraints), caching=True) 28 | 29 | conflict_cache = dict() 30 | def _has_conflict(Rin, seq): 31 | 32 | cons = frozenset(toplevel_list([step.S for step in seq])) 33 | if cons in conflict_cache: 34 | cache = conflict_cache[cons] 35 | if any(is_unsat for dom, is_unsat in cache.items() if Rin <= dom): 36 | return True 37 | elif any(is_unsat is False for dom, is_unsat in cache.items() if Rin >= dom): 38 | return False 39 | else: 40 | conflict_cache[cons] = dict() 41 | 42 | m = cp.Model(list(cons)) 43 | for var, dom in Rin.items(): 44 | m += cp.Table([var], [[val] for val in dom]) 45 | is_unsat = m.solve() is False 46 | 47 | conflict_cache[cons][Rin] = is_unsat 48 | return is_unsat 49 | 50 | 51 | unsat_sequences = dict() 52 | sat_sequences = dict() 53 | 54 | def _try_deletion(Rin, seq): 55 | # test if remaining sequence is still valid 56 | subsequences = dict() # will encounter every subsequence maximum once 57 | 58 | D = Rin 59 | unsat = None 60 | for j, step in enumerate(seq): 61 | if time_limit - (time() - start_time) <= EPSILON: 62 | raise TimeoutError("Filtering timed out") 63 | 64 | str_constraints = str([S for _, S, _ in seq[j:]]) 65 | D_vars = DomainSet({var : D[var] for var in get_variables([S for _,S,_ in seq[j:]])}) 66 | assert str_constraints not in subsequences, "We encountered this sequence already, should not happen!" 67 | subsequences[str_constraints] = D_vars 68 | cons_vars = get_variables(step.S) 69 | 70 | if D <= goal_reduction: 71 | # we can definitely stop 72 | unsat = True 73 | break 74 | elif D <= step.Rin: 75 | # we can deduce Rout from Rin and S, so definitely from D and S 76 | # This holds for all remaining steps in the sequence assuming it was valid in the first place. 77 | # So the sequence is valid 78 | unsat = True 79 | break 80 | elif str_constraints in unsat_sequences and any(D_vars <= dom for dom in unsat_sequences[str_constraints]): 81 | # we decided this sequence ends in UNSAT with less literals, so this one definitely 82 | unsat = True # should never happen as input is maximal 83 | break 84 | elif str_constraints in sat_sequences and any(D_vars >= dom for dom in sat_sequences[str_constraints]): 85 | # we decided this sequence ends in SAT with more literals, so this one definitely 86 | unsat = False 87 | break 88 | elif step.type == "max" and all(D[var] == step.Rin[var] for var in cons_vars): 89 | # no need to propagate, we can copy over the relevant parts of the domains from Rin to Rout 90 | D = DomainSet(dict(D) | {var: step.Rout[var] for var in cons_vars}) 91 | if any(len(dom) == 0 for dom in D.values()): 92 | # unsat, must make entire reduction empty 93 | D = DomainSet({var : frozenset() for var in step.Rin}) 94 | continue 95 | elif _has_conflict(D_vars, seq[j:]): 96 | # there is still a conflict left based on constraints 97 | # can we get there using CP-propagation? 98 | Dcp= copy.deepcopy(D) 99 | for _,Scp,_ in seq[j:]: 100 | Dcp = cp_propagator.propagate(Dcp, Scp, time_limit=time_limit - (time() - start_time)) 101 | # we can get the goal reduction using only CP-steps, so definitely using maxprop steps 102 | if Dcp <= goal_reduction: 103 | unsat = True 104 | break 105 | 106 | # now we have to check what we can deduce from Rin and S 107 | D = propagator.propagate(D, step.S, time_limit=time_limit - (time() - start_time)) 108 | else: 109 | # no conflict left in constraints, definitely not in stepwise manner either 110 | unsat = False 111 | break 112 | 113 | if unsat is None: 114 | unsat = D <= goal_reduction 115 | 116 | dict_to_add = unsat_sequences if unsat else sat_sequences 117 | # store all subsequences we encountered along the way with their initial domain 118 | for seq, dom in subsequences.items(): 119 | if seq in dict_to_add: 120 | dict_to_add[seq].add(dom) 121 | else: 122 | dict_to_add[seq] = {dom} 123 | 124 | return unsat 125 | 126 | # iterate over sequence from back to front 127 | i = len(seq)-1 128 | while i >= 0: 129 | # try deleting step i and check if still valid sequence 130 | if _try_deletion(seq[i].Rin, seq[i+1:]): 131 | seq.pop(i) 132 | i -= 1 133 | 134 | # now fixup all domains in the sequence 135 | # set input domain to given set 136 | seq[0].Rin = DomainSet.from_literals(seq[0].Rin.keys(), {}) 137 | for i, step in enumerate(seq): 138 | step.Rout = propagator.propagate(step.Rin, step.S, time_limit=time_limit-(time() - start_time)) 139 | if i < len(seq)-1: 140 | seq[i+1].Rin = step.Rout 141 | if step.Rout <= goal_reduction: 142 | return seq[:i+1] 143 | 144 | return seq 145 | 146 | def relax_sequence(seq, mus_type="mus", time_limit=3600): 147 | """ 148 | Minimizes input literals for each step. 149 | Keeps a set of literals that need to be derived, only derive those in previous steps. 150 | """ 151 | seq = copy.deepcopy(seq) 152 | 153 | start_time = time() 154 | 155 | all_constraints = set().union(*[set(step.S) for step in seq]) 156 | propagator = MaximalPropagate(constraints = list(all_constraints)) 157 | 158 | if mus_type == "mus": 159 | get_mus = mus 160 | elif mus_type == "smus": 161 | get_mus = smus 162 | else: 163 | raise ValueError(f"Unknown MUS-type: {mus_type}") 164 | 165 | soft = list(seq[-1].Rin.literals()) 166 | if len(soft): 167 | lits_in = mus(soft=list(seq[-1].Rin.literals()), hard=seq[-1].S) 168 | seq[-1].Rin = DomainSet.from_literals(seq[-1].Rin.keys(), lits_in) 169 | R = seq[-1].Rin.literals() 170 | i = len(seq)-2 171 | else: 172 | return seq # length of sequence = 1 173 | 174 | while i >= 0: 175 | if time_limit - (time() - start_time) <= EPSILON: 176 | raise TimeoutError("Relaxing sequence timed out") 177 | step = seq[i] 178 | # find the set of literals derived in this step we actually need later in the sequence 179 | newlits = step.Rout.literals() - step.Rin.literals() 180 | new_required_lits = R & newlits 181 | step.Rout = DomainSet.from_literals(step.Rout.keys(), new_required_lits) 182 | if len(new_required_lits) == 0: 183 | # step can be removed from sequence as no newly derived literal is required 184 | # Note: this case should never occur when running on non-redundant sequences! 185 | seq.pop(i) 186 | else: 187 | # this step derives at least one new literal needed later on in the sequence, so we have to keep it 188 | cons_vars = set(get_variables(step.S)) 189 | # shrink Rin to literals related to variables in constraints 190 | step.Rin = DomainSet({var : dom if var in cons_vars else frozenset(range(var.lb,var.ub+1)) for var, dom in step.Rin.items()}) 191 | soft = step.Rin.literals() 192 | hard = step.S + [cp.any([~lit for lit in step.Rout.literals()])] 193 | # an optimization to mainly use literals in input we need later on in the sequence anyway. 194 | # These literals are derived by a step earlier on in the sequence so we can use them here "for free". 195 | # Other literals in the current input of the step are also derived earlier, but may not actually be necessary 196 | # and can therefore be deleted from outputs of previous steps when chosing the input for this step in a smart way. 197 | # Intuitively, we want R to stay as small as possible! 198 | soft1, hard1 = soft - R, hard + list(R & step.Rin.literals()) 199 | if len(soft1) == 0: 200 | lits_in1 = [] 201 | else: 202 | lits_in1 = get_mus(list(soft1), hard1) 203 | 204 | soft2, hard2 = soft & R, hard + lits_in1 205 | if len(soft2) == 0: 206 | lits_in2 = [] 207 | else: 208 | lits_in2 = get_mus(list(soft2), hard2) 209 | 210 | lits_in = set(lits_in1) | set(lits_in2) 211 | step.Rin = DomainSet.from_literals(step.Rin.keys(), lits_in) 212 | 213 | step.Rout = propagator.propagate(domains=step.Rin, constraints=list(step.S), time_limit=time_limit-(time()-start_time)) 214 | 215 | # update required literals 216 | R = (R - step.Rout.literals()) | step.Rin.literals() 217 | 218 | i -= 1 219 | return make_pertinent(seq) 220 | 221 | 222 | def filter_simple(seq, time_limit=3600): 223 | start_time = time() 224 | 225 | # relax every step 226 | for step in seq: 227 | if time_limit <= EPSILON: 228 | raise TimeoutError("Filtering strongly redundant timed out during relaxation") 229 | mus_start = time() 230 | step.relax(mus_type="smus", solver="ortools", time_limit= time_limit - (time() - start_time)) 231 | 232 | required = seq[-1].Rin.literals() 233 | i = len(seq)-2 # never delete last step 234 | while i >= 0: 235 | step = seq[i] 236 | newlits = step.Rout.literals() - step.Rin.literals() 237 | if len(newlits & required) == 0: 238 | # we do not use any new literal later on, so delete the step 239 | seq.pop(i) 240 | else: # we need the step 241 | required |= step.Rin.literals() 242 | i -= 1 243 | 244 | return seq 245 | 246 | 247 | 248 | def make_pertinent(seq): 249 | derived_already = set() 250 | need_lits = set().union(*[step.Rin.literals() for step in seq]) 251 | 252 | for step in seq[:-1]: # last step contains everything 253 | outlits = ((step.Rout.literals() - step.Rin.literals()) & need_lits) - derived_already 254 | step.Rout = DomainSet.from_literals(step.Rout.keys(), outlits) 255 | derived_already |= outlits 256 | return seq 257 | 258 | 259 | def seq_is_pertinent(seq): 260 | derived_already = set() 261 | for step in seq[:-1]: # last step can derive everything 262 | if len(step.Rout.literals() & derived_already) or len(step.Rin.literals() & step.Rout.literals()): 263 | return False 264 | derived_already |= set(step.Rout.literals()) 265 | return True 266 | -------------------------------------------------------------------------------- /explanations/stepwise/datastructures.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | from frozendict import frozendict 4 | from dataclasses import dataclass 5 | 6 | import cpmpy as cp 7 | from cpmpy.expressions.variables import NegBoolView, _BoolVarImpl 8 | from cpmpy.expressions.core import Comparison 9 | from cpmpy.transformations.get_variables import get_variables 10 | from cpmpy.tools.mus import mus 11 | 12 | EPSILON = 0.01 13 | 14 | 15 | class DomainSet(frozendict): 16 | 17 | def __le__(self, other): 18 | assert self.keys() == other.keys(), "Keys of domain sets do not correspond, probably something is wrong" 19 | for var, vals in self.items(): 20 | if var not in other: 21 | return False 22 | if not vals.issubset(other[var]): 23 | return False 24 | return True 25 | 26 | def __lt__(self, other): 27 | return self <= other and self != other 28 | 29 | def __gt__(self, other): 30 | return other < self 31 | 32 | def __ge__(self, other): 33 | return other <= self 34 | 35 | @staticmethod 36 | def from_vars(vars): 37 | return DomainSet({var: frozenset(range(var.lb, var.ub + 1)) for var in vars}) 38 | 39 | @staticmethod 40 | def from_literals(vars, lits): 41 | # we need vars argument to ensure we have all the variables needed 42 | new_domset = {var: set(range(var.lb, var.ub + 1)) for var in vars} 43 | for lit in lits: 44 | if isinstance(lit, NegBoolView): 45 | new_domset[lit._bv].remove(1) 46 | elif isinstance(lit, _BoolVarImpl): 47 | new_domset[lit].remove(0) 48 | elif isinstance(lit, Comparison) and lit.name == "!=": 49 | var, val = lit.args 50 | new_domset[var].remove(val) 51 | else: 52 | raise ValueError(f"Unknown literal: {lit}") 53 | return DomainSet({var: frozenset(vals) for var, vals in new_domset.items()}) 54 | 55 | def literals(self): 56 | lits = frozenset( 57 | {var != val for var, dom in self.items() for val in range(var.lb, var.ub + 1) if val not in dom}) 58 | return lits 59 | 60 | 61 | @dataclass 62 | class Step: 63 | Rin: DomainSet 64 | S: list 65 | Rout: DomainSet 66 | type: str 67 | guided: bool = True 68 | prev = None 69 | is_relaxed: bool = False 70 | 71 | def __iter__(self): 72 | return iter([self.Rin, self.S, self.Rout]) 73 | 74 | def get_path(self): 75 | if self.prev is None: 76 | return [self] 77 | return self.prev.get_path() + [self] 78 | 79 | def relax(self, mus_type="mus", solver="ortools", time_limit=3600): 80 | from .subset import smus 81 | if mus_type == "mus": 82 | get_mus = mus 83 | elif mus_type == "smus": 84 | get_mus = smus 85 | else: 86 | raise ValueError(f"Unknown MUS-type: {mus_type}") 87 | 88 | # shrink Rin to scope of S 89 | newlits = self.Rout.literals() - self.Rin.literals() 90 | cons_vars = set(get_variables(self.S)) 91 | self.Rin = DomainSet( 92 | {var: dom if var in cons_vars else frozenset(range(var.lb, var.ub + 1)) for var, dom in self.Rin.items()}) 93 | soft = self.Rin.literals() 94 | 95 | if len(soft): 96 | hard = self.S + [cp.any([~lit for lit in newlits])] 97 | lits_in = set(get_mus(list(soft), hard, solver=solver)) 98 | else: 99 | lits_in = set() 100 | 101 | self.Rin = DomainSet.from_literals(self.Rin.keys(), lits_in) 102 | self.Rout = DomainSet.from_literals(self.Rin.keys(), newlits) 103 | self.is_relaxed = True 104 | 105 | def __str__(self): 106 | cons_vars = get_variables(self.S) 107 | out = "Propagated constraints:\n" 108 | for cons in self.S: 109 | out += str(cons) + "\n" 110 | out += "\n" 111 | for var in sorted(cons_vars, key=str): 112 | if len(self.Rin[var]) == (var.ub + 1 - var.lb) and self.Rin[var] == self.Rout[var]: 113 | continue 114 | else: 115 | out += f"{var}:\t" 116 | for val in self.Rin[var]: 117 | if val in self.Rout[var]: 118 | out += f"{val} " 119 | else: 120 | out += f"{val}\u0336 " 121 | out += "\n" 122 | return out 123 | 124 | def __repr__(self): 125 | return self.__str__() 126 | 127 | 128 | # some small tests 129 | if __name__ == "__main__": 130 | from cpmpy import * 131 | -------------------------------------------------------------------------------- /explanations/stepwise/forward.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | import logging 3 | from itertools import combinations 4 | 5 | import random 6 | import numpy as np 7 | 8 | from cpmpy.transformations.get_variables import get_variables 9 | 10 | from .datastructures import Step, DomainSet, EPSILON 11 | from .propagate import CPPropagate, MaximalPropagate, ExactPropagate, MaximalPropagateSolveAll 12 | 13 | 14 | 15 | def connected_network(constraints): 16 | """ 17 | Returns if the constraint network is connected or not 18 | """ 19 | if len(constraints) == 1: 20 | return True # shortcut 21 | scopes = {cons : set(get_variables(cons)) for cons in constraints} 22 | occurs_in = {var : {c for c in constraints if var in scopes[c]} for var in set().union(*scopes.values())} 23 | # start at a random constraint and a random variable 24 | to_visit = {next(iter(scopes[constraints[0]]))} 25 | visited = set() 26 | while len(to_visit): 27 | # try constructing path over constraint graph that visits all 28 | to_visit -= visited 29 | var = to_visit.pop() 30 | # else, var not visisted, add all new vars to frontier reachable from this one using any constraint 31 | visited.add(var) 32 | for cons in occurs_in[var]: 33 | to_visit |= scopes[cons] - visited 34 | return len(visited) == len(set().union(*scopes.values())) 35 | 36 | 37 | 38 | def smallest_next_step(domains, constraints, propagator, time_limit=3600): 39 | """ 40 | Computes the smallest next step given input domains and a list of constraints. 41 | Iterate over all subsets of constraints and check if anything can be propagated 42 | :param domains: a DomainSet representing the domains of variables 43 | :param constraints: a list of CPMpy constraints 44 | :param propagator: a propagator, can be maximal but not required 45 | :return: The smallest step in terms of constraints deriving a new literal 46 | """ 47 | 48 | start_time = time() 49 | 50 | sorted(constraints, key=lambda x: str(x)) 51 | candidates = [] 52 | for size in range(1,len(constraints)+1): 53 | logging.info(f"Propagating constraint sets of size {size}") 54 | #print(f"Propagating constraint sets of size {size}") 55 | 56 | for i, cons in enumerate(combinations(constraints,size)): 57 | if time_limit - (time() - start_time) <= EPSILON: 58 | raise TimeoutError(f"'all_max_steps' timed out after {time() - start_time} seconds") 59 | if size == 2: 60 | if set(get_variables(cons[0])).isdisjoint(set(get_variables(cons[1]))): 61 | # quick check if scopes are disjoint 62 | continue # will never propagate anything new compared to single constraints 63 | elif not connected_network(cons): 64 | continue # will never propagate anything new compared to its strict subsets (which are already checked in previous iteration) 65 | 66 | new_domains = propagator.propagate(domains, list(cons), time_limit=time_limit -(time() - start_time)) 67 | if new_domains == domains: 68 | # nothing propagated, skip 69 | continue 70 | elif new_domains < domains: 71 | # propagated something new, keep step 72 | return Step(domains, list(cons), new_domains, type="max") 73 | else: 74 | raise ValueError("The propagate domains are not a subset of the original domains!") 75 | if len(candidates): 76 | break # found all steps of smallest size, no need to test bigger steps! 77 | raise ValueError("Exhausted all subsets of constraints without sucessfull propagation, is the propagator maximal?") 78 | 79 | 80 | def make_maximal(sequence, propagator): 81 | """ 82 | :param sequence: a sequence of explanations steps 83 | :param propagator: a maximal propagator 84 | :return: the maximal version of the sequence 85 | """ 86 | assert isinstance(propagator, MaximalPropagate), "propagator must be maximal!" 87 | 88 | start_time = time() 89 | 90 | i = 0 91 | D = sequence[0].Rin 92 | while i < len(sequence): 93 | step = sequence[i] 94 | orig_Rin, S, orig_Rout = step 95 | step.Rin = D 96 | cons_vars = get_variables(S) 97 | if step.Rin <= step.Rout: 98 | # nothing usefull propagated 99 | sequence.pop(i) 100 | elif step.type == "max" and all(orig_Rin[var] == D[var] for var in cons_vars): 101 | # no need to propagate 102 | D = DomainSet(dict(D) | {var : orig_Rout[var] for var in cons_vars}) 103 | step.Rout = D 104 | i += 1 105 | else: 106 | step.Rin = D # ensure domains are correct for other vars as well 107 | # need to propagate 108 | D = propagator.propagate(D, list(S), time() - (time() - start_time)) 109 | step.Rout = D 110 | step.type="max" 111 | i += 1 112 | 113 | return sequence 114 | 115 | 116 | def construct_greedy(constraints, goal_reduction, time_limit, seed): 117 | 118 | start_time = time() 119 | random.seed(seed) 120 | np.random.seed(seed) 121 | 122 | # max_propagator = ExactPropagate(constraints=constraints, caching=False) 123 | # max_propagator = CPPropagate(constraints=constraints, caching=False) 124 | #max_propagator = MaximalPropagateSolveAll(constraints=constraints, caching=True) 125 | max_propagator = MaximalPropagate(constraints=constraints, caching=True) 126 | 127 | domains = DomainSet({var : frozenset(range(var.lb, var.ub+1)) for var in get_variables(constraints)}) 128 | seq = [Step(domains, [], domains, type="max", guided=False)] 129 | 130 | while 1: 131 | if time_limit - (time() - start_time) <= EPSILON: 132 | raise TimeoutError(f"'construct_beam' timed out after {time() - start_time} seconds") 133 | 134 | prev_step = seq[-1] 135 | _,_,domains = prev_step 136 | logging.info(f"{sum(len(dom) for dom in domains.values())} literals left") 137 | #print(f"{sum(len(dom) for dom in domains.values())} literals left") 138 | 139 | # find next smallest step 140 | next_step = smallest_next_step(domains, constraints, max_propagator, time_limit=time_limit - (time() - start_time)) 141 | seq.append(next_step) 142 | if next_step.Rout <= goal_reduction: 143 | break 144 | 145 | return seq[1:] # prune first dummy state 146 | -------------------------------------------------------------------------------- /explanations/stepwise/propagate.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import time 3 | 4 | import cpmpy as cp 5 | from cpmpy.transformations.get_variables import get_variables 6 | from cpmpy.transformations.normalize import toplevel_list 7 | from cpmpy.expressions.utils import is_any_list 8 | 9 | 10 | from .datastructures import DomainSet, EPSILON 11 | 12 | def propagate(constraints, type="max"): 13 | if type == "max": 14 | cls = MaximalPropagateSolveAll 15 | elif type == "cp": 16 | cls = CPPropagate 17 | 18 | constraints = toplevel_list(constraints) 19 | prop = cls(constraints) 20 | doms = {var : frozenset(range(var.lb, var.ub+1)) for var in get_variables(constraints)} 21 | if type == "cp": 22 | return prop.propagate(doms, constraints, time_limit=1000, only_unit_propagation=False) 23 | return prop.propagate(doms, constraints, time_limit=1000) 24 | 25 | 26 | class Propagator: 27 | 28 | def __init__(self, constraints:list, caching=True): 29 | # bi-level cache with level 1 = constraint(s), level 2 = domains, 30 | self.cache = dict() if caching else None 31 | self.vars = set(get_variables(constraints)) 32 | self.scope_cache = dict() 33 | assert is_any_list(constraints), f"expected list but got {type(constraints)}" 34 | for cons in constraints: 35 | self.scope_cache[cons] = frozenset(get_variables(cons)) 36 | 37 | 38 | def _probe_cache(self, domains, constraints) -> DomainSet: 39 | if self.cache is None: return None 40 | 41 | if not isinstance(constraints, list): 42 | constraints = [constraints] 43 | constraints = frozenset(constraints) 44 | if constraints not in self.cache: 45 | return None 46 | 47 | cons_vars = set().union(*[self.scope_cache[cons] for cons in constraints]) 48 | cons_domains = DomainSet({var : domains[var] for var in cons_vars}) 49 | new_domains = self.cache[constraints].get(cons_domains) 50 | if new_domains is not None: 51 | # are we in the UNSAT case? 52 | if any(len(dom) == 0 for dom in new_domains.values()): 53 | return DomainSet({var : frozenset() for var in domains}) 54 | 55 | new_domains = {var : vals for var, vals in new_domains.items()} # make it a normal dict for now 56 | # copy domains of variables not in scope 57 | for var, orig_domain in domains.items(): 58 | if var not in cons_vars: 59 | new_domains[var] = orig_domain 60 | return DomainSet(new_domains) 61 | 62 | 63 | def _fill_cache(self, domains, constraints, new_domains): 64 | if self.cache is None: return None 65 | if not isinstance(constraints, list): 66 | constraints = [constraints] 67 | 68 | constraints = frozenset(constraints) 69 | if constraints not in self.cache: 70 | self.cache[constraints] = dict() 71 | 72 | cons_vars = set().union(*[self.scope_cache[cons] for cons in constraints]) 73 | domains = DomainSet({var : domains[var] for var in cons_vars}) 74 | new_domains = DomainSet({var : new_domains[var] for var in cons_vars}) 75 | self.cache[constraints][domains] = new_domains 76 | 77 | 78 | def propagate(self, domains, constraints, time_limit): 79 | raise NotImplementedError(f"Propagation for propagator {type(self)} not implemented") 80 | 81 | 82 | 83 | class CPPropagate(Propagator): 84 | 85 | prop_kwargs = dict( 86 | cp_model_probing_level = 0, 87 | presolve_bve_threshold = -1, 88 | presolve_probing_deterministic_time_limit = 0, 89 | presolve_blocked_clause = False, 90 | presolve_use_bva = False, 91 | max_presolve_iterations = 1, 92 | table_compression_level = 0, 93 | merge_no_overlap_work_limit = 0, 94 | merge_at_most_one_work_limit = 0, 95 | presolve_substitution_level = 0, 96 | presolve_inclusion_work_limit = 0, 97 | 98 | ) 99 | 100 | def propagate(self, domains, constraints, time_limit, only_unit_propagation=True): 101 | 102 | # check cache 103 | cached = self._probe_cache(domains, constraints) 104 | if cached is not None: return cached 105 | 106 | # only care about domains of variables in constraints 107 | cons_vars = set(get_variables(constraints)) 108 | 109 | solver = cp.SolverLookup.get("ortools") 110 | solver += constraints 111 | for var in cons_vars: # set leftover domains of vars 112 | solver += cp.Table([var],[[val] for val in domains[var]]) 113 | 114 | req_kwargs = dict( 115 | stop_after_presolve=True, 116 | keep_all_feasible_solutions_in_presolve=True, 117 | fill_tightened_domains_in_response=True 118 | ) 119 | 120 | if only_unit_propagation: 121 | solver.solve(**req_kwargs, **self.prop_kwargs) 122 | else: 123 | solver.solve(**req_kwargs) 124 | 125 | bounds = solver.ort_solver.ResponseProto().tightened_variables 126 | 127 | if len(bounds) == 0: 128 | # UNSAT, no propagation possible 129 | prop_dom = {var: set() for var in domains} 130 | 131 | else: 132 | prop_dom = dict() 133 | for var, orig_dom in domains.items(): 134 | if var not in cons_vars: 135 | prop_dom[var] = orig_dom # unchanged 136 | continue # variable not in constraints 137 | 138 | ort_var = solver.solver_var(var) 139 | var_bounds = bounds[ort_var.Index()].domain 140 | 141 | lbs = [val for i, val in enumerate(var_bounds) if i % 2 == 0] 142 | ubs = [val for i, val in enumerate(var_bounds) if i % 2 == 1] 143 | 144 | prop_dom[var] = set() 145 | for lb, ub in zip(lbs, ubs): 146 | prop_dom[var] |= set(range(lb, ub + 1)) 147 | 148 | for val, dom in prop_dom.items(): 149 | prop_dom[val] = frozenset(dom) 150 | 151 | # store new domains in cache 152 | self._fill_cache(domains, constraints, prop_dom) 153 | return DomainSet(prop_dom) 154 | 155 | 156 | class MaximalPropagate(Propagator): 157 | """ 158 | Maximal propagator 159 | """ 160 | 161 | def __init__(self, constraints, caching=True): 162 | super().__init__(constraints, caching) 163 | self.cp_prop = CPPropagate(constraints, caching=caching) 164 | 165 | def propagate(self, domains, constraints, time_limit): 166 | start_time = time.time() 167 | 168 | # check cache 169 | cached = self._probe_cache(domains, constraints) 170 | if cached is not None: return cached 171 | 172 | # do a very quick CP-prop first so domains are tighter 173 | domains = self.cp_prop.propagate(domains, constraints, time_limit, only_unit_propagation=False) 174 | if any(len(dom) == 0 for dom in domains.values()): 175 | return domains # unsat 176 | 177 | 178 | # only care about variables in constraints 179 | cons_vars = set(get_variables(constraints)) 180 | 181 | solver = cp.SolverLookup.get("ortools") 182 | solver += constraints 183 | for var in cons_vars: # set leftover domains of vars 184 | solver += cp.Table([var], [[val] for val in domains[var]]) 185 | 186 | # check if model is UNSAT 187 | if solver.solve() is False: 188 | prop_dom = {var : frozenset() for var in domains} 189 | 190 | else: 191 | to_visit = {var : {val for val in domains[var]} for var in cons_vars} 192 | while solver.solve(): 193 | if time_limit - (time.time() - start_time) <= EPSILON: 194 | raise TimeoutError("Maximal Propagate timed out") 195 | for var in cons_vars: 196 | to_visit[var].discard(var.value()) 197 | # ensure next iteration visits at least one new value for a variable 198 | lits = [var == val for var, vals in to_visit.items() for val in vals] 199 | if len(lits) == 0: 200 | break 201 | solver += cp.any(lits) 202 | 203 | 204 | prop_dom = dict() 205 | for var, dom in domains.items(): 206 | if var not in cons_vars: # unchanged domains 207 | prop_dom[var] = frozenset(dom) 208 | else: 209 | prop_dom[var] = frozenset(dom - to_visit[var]) 210 | 211 | prop_dom = DomainSet(prop_dom) 212 | # store new domains in cache 213 | self._fill_cache(domains, constraints, prop_dom) 214 | return prop_dom 215 | 216 | 217 | class MaximalPropagateSolveAll(MaximalPropagate): 218 | """ Alternative implementation of maximal propagate using OR-tools solveAll function""" 219 | 220 | def propagate(self, domains, constraints, time_limit): 221 | start_time = time.time() 222 | 223 | # check cache 224 | cached = self._probe_cache(domains, constraints) 225 | if cached is not None: return cached 226 | 227 | # do a very quick CP-prop first so domains are tighter 228 | cp_propped_domains = self.cp_prop.propagate(domains, constraints, time_limit, only_unit_propagation=False) 229 | if any(len(dom) == 0 for dom in cp_propped_domains.values()): 230 | return cp_propped_domains # unsat 231 | 232 | # only care about variables in constraints 233 | cons_vars = set(get_variables(constraints)) 234 | 235 | solver = cp.SolverLookup.get("ortools") 236 | solver += constraints 237 | for var in cons_vars: # set leftover domains of vars 238 | solver += cp.Table([var], [[val] for val in cp_propped_domains[var]]) 239 | 240 | 241 | visisted = {var : set() for var in cons_vars} 242 | def callback(): 243 | for var in cons_vars: 244 | visisted[var].add(var.value()) 245 | 246 | solver.solveAll(display=callback) 247 | 248 | prop_dom = dict() 249 | for var, dom in domains.items(): 250 | if var not in cons_vars: # unchanged domains 251 | prop_dom[var] = frozenset(dom) 252 | else: 253 | prop_dom[var] = frozenset(visisted[var]) 254 | prop_dom = DomainSet(prop_dom) 255 | # store new domains in cache 256 | self._fill_cache(domains, constraints, prop_dom) 257 | return prop_dom 258 | 259 | class ExactPropagate(MaximalPropagate): 260 | 261 | def __init__(self, constraints, caching=True): 262 | super().__init__(constraints, caching) 263 | self.solver = cp.SolverLookup.get("exact") 264 | # post reified constraints to solver 265 | self.cons_dict = dict() 266 | for cons in constraints: 267 | bv = cp.boolvar(name=f"->{cons}") 268 | self.cons_dict[cons] = bv 269 | self.solver += bv.implies(cons) 270 | 271 | # initialize solver and do all necesessary things in background 272 | self.solver.xct_solver.setOption("verbosity", "0") 273 | assert self.solver.solve() 274 | 275 | # keep info of last call 276 | self.last_call = {"domains": None, "constraints": None, "vars": None} 277 | 278 | def propagate(self, domains, constraints, time_limit): 279 | start_time = time.time() 280 | 281 | # check cache 282 | cached = self._probe_cache(domains, constraints) 283 | if cached is not None: 284 | return cached 285 | 286 | # do a very quick CP-prop first so domains are tighter 287 | cp_propped_domains = self.cp_prop.propagate(domains, constraints, time_limit, only_unit_propagation=False) 288 | if any(len(dom) == 0 for dom in cp_propped_domains.values()): 289 | return cp_propped_domains # unsat 290 | 291 | if not is_any_list(constraints): 292 | constraints = [constraints] 293 | # cache miss, set assumptions to solver 294 | cons_vars = get_variables(constraints) 295 | 296 | if cp_propped_domains != self.last_call["domains"]: 297 | # domains changed, reset all assumptions (also for constraints as this is easier) 298 | #print("Resetting domains") 299 | self.solver.xct_solver.clearAssumptions() 300 | # set new assumptions for domains 301 | for var, dom in cp_propped_domains.items(): 302 | self.solver.xct_solver.setAssumption(self.solver.solver_var(var), list(dom)) 303 | 304 | # store domains of this call 305 | self.last_call["domains"] = cp_propped_domains 306 | self.last_call["constraints"] = None # reset all assumptions, so also the ones for the constraints 307 | 308 | # set assumptions for constraints 309 | if set(constraints) != self.last_call["constraints"]: 310 | if self.last_call["constraints"] is not None: 311 | old_cons_assump = self.solver.solver_vars([self.cons_dict[cons] for cons in self.last_call["constraints"]]) 312 | # delete assumptions for previous constraints 313 | for xct_assump in old_cons_assump: 314 | self.solver.xct_solver.clearAssumption(xct_assump) 315 | 316 | # set assumptions for new constraints 317 | new_cons_assump = self.solver.solver_vars([self.cons_dict[cons] for cons in constraints]) 318 | for xct_assump in new_cons_assump: 319 | self.solver.xct_solver.setAssumption(xct_assump, [1]) 320 | 321 | # store constraints of this call 322 | self.last_call["constraints"] = frozenset(constraints) 323 | 324 | # do the propagation 325 | # self.solver.xct_solver.setOption("timeout", str(int(time_limit - (time.time() - start_time)))) 326 | time_limit = time_limit - (time.time() - start_time) 327 | new_domains = self.solver.xct_solver.pruneDomains(vars=self.solver.solver_vars(cons_vars), timeout=time_limit) 328 | if len(new_domains) == 0: 329 | raise TimeoutError("Exact propagate timed out") 330 | 331 | 332 | # are we in the unsat case? 333 | if any(len(dom) == 0 for dom in new_domains): 334 | prop_dom = {var : frozenset() for var in cp_propped_domains} 335 | 336 | else: 337 | prop_dom = {var : frozenset(new_domains[i]) for i, var in enumerate(cons_vars)} 338 | # make cons_vars a set 339 | cons_vars = set(cons_vars) 340 | for var, orig_dom in domains.items(): 341 | if var not in cons_vars: 342 | # unchanged domain 343 | prop_dom[var] = orig_dom 344 | 345 | prop_dom = DomainSet(prop_dom) 346 | # store new domains in cache 347 | self._fill_cache(domains, constraints, prop_dom) 348 | 349 | return prop_dom 350 | 351 | 352 | 353 | 354 | -------------------------------------------------------------------------------- /explanations/subset.py: -------------------------------------------------------------------------------- 1 | 2 | import cpmpy as cp 3 | from cpmpy.exceptions import CPMpyException 4 | from cpmpy.transformations.normalize import toplevel_list 5 | 6 | import copy 7 | 8 | def mus(soft, hard): 9 | 10 | # try reification of all soft constraints 11 | try: 12 | return cpmpy.tools.mus.mus(soft, hard) 13 | except CPMpyException: 14 | return cpmpy.tools.mus.mus_naive(soft, hard) 15 | 16 | def maxsat(soft, hard=[]): 17 | 18 | soft = toplevel_list(soft, merge_and=False) 19 | assump = cp.boolvar(shape=len(soft)) 20 | dmap = dict(zip(assump, soft)) 21 | 22 | m = cp.Model(hard) 23 | m += assump.implies(soft) 24 | m.maximize(cp.sum(assump)) 25 | 26 | assert m.solve() 27 | 28 | return [dmap[a] for a in assump if a.value()] 29 | 30 | def mcs(soft, hard=[], solver="ortools"): 31 | 32 | soft = toplevel_list(soft, merge_and=False) 33 | assump = cp.boolvar(shape=len(soft)) 34 | s = cp.SolverLookup.get(solver) 35 | s += hard 36 | s += assump.implies(soft) 37 | 38 | s.solution_hint(assump, [1]*len(assump)) 39 | assert s.solve() 40 | 41 | dmap = dict(zip(assump, soft)) 42 | mcs = _sat_grow(s, set() , dmap) 43 | return [dmap[a] for a in mcs] 44 | 45 | 46 | def optimal_mcs(soft, hard=[], solver="ortools"): 47 | 48 | soft = toplevel_list(soft, merge_and=False) 49 | assump = cp.boolvar(shape=len(soft)) 50 | s = cp.SolverLookup.get(solver) 51 | s += hard 52 | s += assump.implies(soft) 53 | 54 | s.maximize(cp.sum(assump)) 55 | assert s.solve() 56 | 57 | dmap = dict(zip(assump, soft)) 58 | return [dmap[a] for a in assump if a.value() is False] 59 | 60 | 61 | def _sat_grow(solver, sat_subset, dmap): 62 | """ 63 | Find a superset of "subset" which is still satisfiable, not the largest one per se. 64 | """ 65 | # to_check = _greedy_grow(dmap) 66 | to_check = set(dmap.keys()) - sat_subset 67 | sat_subset = copy.copy(sat_subset) 68 | while len(to_check): 69 | test = to_check.pop() 70 | new_set = copy.copy(sat_subset) 71 | new_set.add(test) 72 | # solver.solution_hint(list(new_set), len(new_set)*[1]) 73 | if solver.solve(assumptions=list(new_set)): 74 | # is sat, so add to sat subset 75 | sat_subset = {assump for assump, cons in dmap.items() if assump.value() or cons.value()} 76 | to_check -= sat_subset 77 | 78 | return set(dmap.keys()) - sat_subset 79 | 80 | 81 | def _greedy_grow(dmap): 82 | """ 83 | Very cheaply check the values of the decision variables and construct sat set from that 84 | """ 85 | sat_subset = {assump for assump, cons in dmap.items() if assump.value() or cons.value()} 86 | return set(dmap.keys()) - sat_subset 87 | 88 | 89 | def _corr_subsets(subset, dmap, solver, hard): 90 | sat_subset = {s for s in subset} 91 | corr_subsets = [] 92 | vars= list(dmap.keys()) 93 | solver.solution_hint(vars, [1]*len(vars)) 94 | while solver.solve(assumptions=list(sat_subset)): 95 | """ 96 | Change the grow method here if wanted! 97 | MaxSAT grow will probably be slow but result in very small sets to hit (GOOD!) 98 | SAT-grow will probably be a little faster but greedily finds a small set to hit 99 | Greedy-grow will not do ANY solving and simply exploit the values in the current solution 100 | """ 101 | # corr_subset = _maxsat_grow(sat_subset) 102 | # corr_subset = _sat_grow(solver, sat_subset, dmap) 103 | corr_subset = _greedy_grow(dmap) 104 | if len(corr_subset) == 0: 105 | return corr_subsets 106 | 107 | sat_subset |= corr_subset 108 | corr_subsets.append(corr_subset) 109 | solver.solution_hint(vars, [1] * len(vars)) 110 | return corr_subsets 111 | 112 | def ocus_oneof(soft, hard=[], oneof_idxes=[], weights=1, solver="ortools", hs_solver="gurobi"): 113 | 114 | soft = toplevel_list(soft, merge_and=False) 115 | assump = cp.boolvar(shape=len(soft), name="assump") 116 | if len(soft) == 1: 117 | assump = cp.cpm_array([assump]) 118 | 119 | m = cp.Model(hard + [assump.implies(soft)]) # each assumption variable implies a candidate 120 | dmap = dict(zip(assump, soft)) 121 | s = cp.SolverLookup.get(solver, m) 122 | assert not s.solve(assumptions=assump), "MUS: model must be UNSAT" 123 | 124 | # hitting set solver stuff 125 | hs_solver = cp.SolverLookup.get(hs_solver) 126 | if len(oneof_idxes): 127 | hs_solver += cp.sum(assump[oneof_idxes]) == 1 128 | hs_solver.minimize(cp.sum(weights * assump)) 129 | 130 | while hs_solver.solve(): 131 | 132 | subset = assump[assump.value() == 1] 133 | if s.solve(assumptions=subset) is True: 134 | # grow subset while staying satisfiable under assumptions 135 | for grown in _corr_subsets(subset, dmap, s, hard=hard): 136 | hs_solver += cp.sum(grown) >= 1 137 | else: 138 | return [dmap[a] for a in subset] 139 | 140 | def smus(soft, hard=[], weights=1, solver="ortools", hs_solver="gurobi"): 141 | return ocus_oneof(soft, hard, [], weights, solver, hs_solver) 142 | 143 | def omus(soft, hard=[], weights=1, solver="ortools", hs_solver="gurobi"): 144 | return ocus_oneof(soft, hard, [], weights, solver, hs_solver) 145 | -------------------------------------------------------------------------------- /hands-on-tutorial slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/hands-on-tutorial slides.pdf -------------------------------------------------------------------------------- /img/allcons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/allcons.png -------------------------------------------------------------------------------- /img/app_explanations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/app_explanations.png -------------------------------------------------------------------------------- /img/beluga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/beluga.png -------------------------------------------------------------------------------- /img/change_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/change_model.png -------------------------------------------------------------------------------- /img/changing_solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/changing_solution.png -------------------------------------------------------------------------------- /img/chatopt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/chatopt.png -------------------------------------------------------------------------------- /img/chatopt1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/chatopt1.png -------------------------------------------------------------------------------- /img/chatopt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/chatopt2.png -------------------------------------------------------------------------------- /img/chatopt3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/chatopt3.png -------------------------------------------------------------------------------- /img/chatopt4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/chatopt4.png -------------------------------------------------------------------------------- /img/coloring_mcs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/coloring_mcs.png -------------------------------------------------------------------------------- /img/coloring_mus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/coloring_mus.png -------------------------------------------------------------------------------- /img/cpmpy-intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/cpmpy-intro.png -------------------------------------------------------------------------------- /img/cpmpy_transformations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/cpmpy_transformations.png -------------------------------------------------------------------------------- /img/erc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/erc.jpg -------------------------------------------------------------------------------- /img/explain_step-wise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/explain_step-wise.png -------------------------------------------------------------------------------- /img/explain_unsat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/explain_unsat.png -------------------------------------------------------------------------------- /img/fixing_mcs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/fixing_mcs.png -------------------------------------------------------------------------------- /img/fixing_relax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/fixing_relax.png -------------------------------------------------------------------------------- /img/hittingset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/hittingset.png -------------------------------------------------------------------------------- /img/interaction_figure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/interaction_figure.png -------------------------------------------------------------------------------- /img/interaction_figure2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/interaction_figure2.png -------------------------------------------------------------------------------- /img/interaction_figure3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/interaction_figure3.png -------------------------------------------------------------------------------- /img/interaction_figure4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/interaction_figure4.png -------------------------------------------------------------------------------- /img/intro_ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/intro_ai.png -------------------------------------------------------------------------------- /img/inverse_opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/inverse_opt.png -------------------------------------------------------------------------------- /img/kul.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/kul.jpg -------------------------------------------------------------------------------- /img/maxconsequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/maxconsequence.png -------------------------------------------------------------------------------- /img/mcs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/mcs.png -------------------------------------------------------------------------------- /img/model_reconciliation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/model_reconciliation.png -------------------------------------------------------------------------------- /img/model_solve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/model_solve.png -------------------------------------------------------------------------------- /img/mss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/mss.png -------------------------------------------------------------------------------- /img/mus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/mus.png -------------------------------------------------------------------------------- /img/mus_assum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/mus_assum.png -------------------------------------------------------------------------------- /img/musses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/musses.png -------------------------------------------------------------------------------- /img/nurse_rost_prob.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/nurse_rost_prob.jpg -------------------------------------------------------------------------------- /img/ocus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/ocus.png -------------------------------------------------------------------------------- /img/onestep_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/onestep_example.png -------------------------------------------------------------------------------- /img/onestep_expl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/onestep_expl.png -------------------------------------------------------------------------------- /img/onestep_mus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/onestep_mus.png -------------------------------------------------------------------------------- /img/order_delmus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/order_delmus.png -------------------------------------------------------------------------------- /img/plant_layout_expl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/plant_layout_expl.png -------------------------------------------------------------------------------- /img/prob2sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/prob2sol.png -------------------------------------------------------------------------------- /img/qr-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/qr-code.png -------------------------------------------------------------------------------- /img/quickxplain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/quickxplain.png -------------------------------------------------------------------------------- /img/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/slack.png -------------------------------------------------------------------------------- /img/slide_cdcl1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/slide_cdcl1.png -------------------------------------------------------------------------------- /img/slide_cdcl2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/slide_cdcl2.png -------------------------------------------------------------------------------- /img/slide_cdcl3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/slide_cdcl3.png -------------------------------------------------------------------------------- /img/slide_cdcl4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/slide_cdcl4.png -------------------------------------------------------------------------------- /img/smus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/smus.png -------------------------------------------------------------------------------- /img/smus_efficient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/smus_efficient.png -------------------------------------------------------------------------------- /img/solutions_vizual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/solutions_vizual.png -------------------------------------------------------------------------------- /img/stepwise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/stepwise.png -------------------------------------------------------------------------------- /img/stepwise_pseudo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/stepwise_pseudo.png -------------------------------------------------------------------------------- /img/task_alloc_expl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/task_alloc_expl.png -------------------------------------------------------------------------------- /img/tuples_logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/tuples_logo.jpeg -------------------------------------------------------------------------------- /img/tutorial_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/tutorial_thumbnail.png -------------------------------------------------------------------------------- /img/why_not_better.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/why_not_better.png -------------------------------------------------------------------------------- /img/why_not_better2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/why_not_better2.png -------------------------------------------------------------------------------- /img/workforce_ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/workforce_ex.png -------------------------------------------------------------------------------- /img/workforce_restore_feas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/workforce_restore_feas.png -------------------------------------------------------------------------------- /img/workforce_restore_infeas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/workforce_restore_infeas.png -------------------------------------------------------------------------------- /img/workforce_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/workforce_review.png -------------------------------------------------------------------------------- /img/workforce_solve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/workforce_solve.png -------------------------------------------------------------------------------- /img/workforce_workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CPMpy/XCP-explain/d3246ba0679aff07ca74b66cd27eac9d59a1c1c7/img/workforce_workflow.png -------------------------------------------------------------------------------- /read_data.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import re 3 | from io import StringIO 4 | import pandas as pd 5 | 6 | def tag_to_data(string, tag, skip_lines=0, datatype=pd.DataFrame, *args, **kwargs): 7 | 8 | regex = rf'{tag}[\s\S]*?($|(?=\n\s*\n))' 9 | match = re.search(regex, string) 10 | 11 | data = "\n".join(match.group().split("\n")[skip_lines+1:]) 12 | if datatype == pd.DataFrame: 13 | kwargs = {"header":0, "index_col":0} | kwargs 14 | df = pd.read_csv(StringIO(data), *args, **kwargs) 15 | return df.rename(columns=lambda x: x.strip()) 16 | return datatype(data, *args, **kwargs) 17 | 18 | 19 | 20 | @dataclasses.dataclass 21 | class SchedulingProblem: 22 | horizon : int = 0 23 | shifts: pd.DataFrame = None 24 | staff: pd.DataFrame = None 25 | days_off : pd.DataFrame = None 26 | shift_on : pd.DataFrame = None 27 | shift_off : pd.DataFrame = None 28 | cover : pd.DataFrame = None 29 | 30 | 31 | def get_data(fname): 32 | from faker import Faker 33 | fake = Faker() 34 | fake.seed_instance(0) 35 | 36 | with open(fname, "r") as f: 37 | string = f.read() 38 | 39 | problem = SchedulingProblem() 40 | 41 | problem.horizon = tag_to_data(string, "SECTION_HORIZON", skip_lines=2, datatype=int) 42 | shifts = tag_to_data(string, "SECTION_SHIFTS", names=["ShiftID", "Length", "cannot follow"], 43 | dtype={'ShiftID':str, 'Length':int, 'cannot follow':str}) 44 | shifts.fillna("", inplace=True) 45 | shifts["cannot follow"] = shifts["cannot follow"].apply(lambda val : val.split("|")) 46 | problem.shifts = shifts 47 | 48 | staff = tag_to_data(string, "SECTION_STAFF", index_col=False) 49 | maxes = staff["MaxShifts"].str.split("|", expand=True) 50 | for col in maxes: 51 | shift_id = maxes[col].iloc[0].split("=")[0] 52 | column = maxes[col].apply(lambda x : x.split("=")[1]) 53 | staff[f"max_shifts_{shift_id}"] = column.astype(int) 54 | 55 | staff["name"] = [fake.unique.first_name() for _ in staff.index] 56 | problem.staff = staff 57 | 58 | days_off = tag_to_data(string, "SECTION_DAYS_OFF", datatype=str) 59 | # process string to be EmployeeID, Day off for each line 60 | rows = [] 61 | for line in days_off.split("\n")[1:]: 62 | employee_id , *days = line.split(",") 63 | rows += [dict(EmployeeID=employee_id, DayIndex= int(d)) for d in days] 64 | problem.days_off = pd.DataFrame(rows) 65 | 66 | 67 | # problem.days_off = tag_to_data(string, "SECTION_DAYS_OFF", names=["EmployeeID", "DayIdx"]) 68 | # problem.days_off["DayIdx"] = problem.days_off["DayIdx"].apply(lambda val : val if isinstance(val, int) 69 | # else [int(x) for x in val.split(",")]) 70 | problem.shift_on = tag_to_data(string, "SECTION_SHIFT_ON_REQUESTS", index_col=False) 71 | problem.shift_off = tag_to_data(string, "SECTION_SHIFT_OFF_REQUESTS", index_col=False) 72 | problem.cover = tag_to_data(string, "SECTION_COVER", index_col=False) 73 | 74 | return problem 75 | 76 | 77 | if __name__ == "__main__": 78 | 79 | problem = get_data("Benchmarks/Instance12.txt") 80 | 81 | print(problem) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Requirements for solving 2 | cpmpy 3 | ortools 4 | exact # for fast incremental solving 5 | numpy 6 | 7 | # Requirements for visualization 8 | pandas==2.1.4 9 | matplotlib 10 | 11 | # For running the notebook 12 | jupyter 13 | nbclassic 14 | rise 15 | 16 | # Utils 17 | faker 18 | frozendict 19 | networkx 20 | -------------------------------------------------------------------------------- /visualize.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | import numpy as np 4 | 5 | import matplotlib.pyplot as plt 6 | 7 | def visualize(sol, factory, highlight_cover=False): 8 | weeks = [f"Week {i + 1}" for i in range(factory.data.horizon // 7)] 9 | weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 10 | nurses = factory.data.staff['name'].tolist() 11 | 12 | df = pd.DataFrame(sol, 13 | columns=pd.MultiIndex.from_product((weeks, weekdays), names=("Week", "Day")), 14 | index=factory.data.staff.name) 15 | 16 | 17 | total_minutes = df.map(lambda i : ([0] + list(factory.data.shifts.Length))[i] if i is not None else 0).sum(axis=1).astype(int) 18 | 19 | mapping = factory.idx_to_name 20 | df = df.map(lambda v: mapping[v] if v is not None else '') # convert to shift names 21 | 22 | real_shifts = sorted(set(factory.shift_name_to_idx) - {"-"}) 23 | total_shifts = pd.DataFrame(columns=pd.MultiIndex.from_product([["#Shifts"], real_shifts]), index=df.index) 24 | for shift_type in real_shifts: 25 | total_shifts[("#Shifts", shift_type)] = (df == shift_type).sum(axis=1) 26 | 27 | for shift_type in real_shifts: 28 | sums = (df == shift_type).sum() # cover for each shift type 29 | req = factory.data.cover["Requirement"][factory.data.cover["ShiftID"] == shift_type] 30 | req.index = sums.index 31 | df.loc[f'Cover {shift_type}'] = sums.astype(str) + "/" + req.astype(str) 32 | 33 | 34 | df = pd.concat([df, total_shifts], axis=1) 35 | df["#Minutes"] = total_minutes 36 | df = df.fillna(0) 37 | df["#Shifts"] = df["#Shifts"].astype(int) 38 | df["#Minutes"] = df["#Minutes"].astype(int) 39 | 40 | 41 | subset = (df.index.tolist()[:-len(factory.data.shifts)], df.columns[:-(len(real_shifts)+1)]) 42 | style = df.style.set_table_styles([{'selector': '.data', 'props': [('text-align', 'center')]}, 43 | {'selector': '.col_heading', 'props': [('text-align', 'center')]}, 44 | {'selector': '.col7', 'props': [('border-left',"2px solid black")]}]) 45 | style = style.map(lambda v: 'border: 1px solid black', subset=subset) 46 | style = style.map(color_shift, factory=factory, subset=subset) # color cells 47 | 48 | if highlight_cover is True: 49 | 50 | def highlight(val): 51 | fill, req = val.split('/') 52 | if fill == req: 53 | return '' 54 | return 'color : red' 55 | subset = (df.index.tolist()[-len(factory.data.shifts):], df.columns[:-2]) 56 | style = style.map(highlight, subset=subset) 57 | 58 | return style 59 | 60 | def color_shift(shift, factory): 61 | # cmap = ["yellow", "blue","red", "orange", "cyan"] 62 | cmap = plt.get_cmap("Set3") # https://matplotlib.org/2.0.2/examples/color/colormaps_reference.html 63 | if shift is None or shift == '' or shift == '-': 64 | return 'background-color: white' 65 | # return f"background-color: {cmap(factory.shift_name_to_idx[shift])}" 66 | r,g,b = (round(255*val) for val in cmap.colors[factory.shift_name_to_idx[shift]]) 67 | return f"background-color: rgb({r},{g},{b})" 68 | 69 | def highlight_changes(new_sol, old_sol, factory): 70 | 71 | style = visualize(new_sol, factory) 72 | shape = style.data.shape 73 | 74 | neq = new_sol != old_sol 75 | diff0 = shape[0] - neq.shape[0] 76 | diff1 = shape[1] - neq.shape[1] 77 | neq = np.pad(neq, ((0, diff0), (0, diff1)), constant_values=False) 78 | 79 | df_css = pd.DataFrame(neq) 80 | df_css.index = style.index 81 | df_css.columns = style.columns 82 | 83 | df_css = df_css.map(lambda x: "border: 5px solid lawngreen" if x else "") 84 | return style.apply(apply_styles, styles=df_css, axis=None) 85 | 86 | # Function to apply CSS styles to each cell 87 | def apply_styles(x, styles): 88 | # The styles DataFrame is returned directly 89 | return styles 90 | 91 | 92 | def visualize_constraints(constraints, nurse_view, factory, do_clear=True): 93 | if do_clear: 94 | nurse_view.clear() 95 | 96 | style = visualize(nurse_view.value(), factory) 97 | 98 | df_css = pd.DataFrame(index=style.index, columns=style.columns) 99 | df_css.fillna("", inplace=True) 100 | for cons in constraints: 101 | if hasattr(cons, "visualize"): 102 | cons.visualize(df_css) 103 | return style.apply(apply_styles, styles=df_css, axis=None) 104 | 105 | def visualize_step(step, nurse_view, factory): 106 | E, S, N = step 107 | print(f"Propagating constraint: {next(iter(S))}") 108 | if any(len(vals) == 0 for vals in N.values()): 109 | # found UNSAT 110 | return visualize_constraints(S, nurse_view, factory=factory, do_clear=False) 111 | else: 112 | for v in E: 113 | if E[v] > N[v]: 114 | # derived something for this variable 115 | assert len(N[v]) <= 1, "only allow assigments here... (TODO? how to visualize negative facts?)" 116 | # hacky way to find index 117 | r = int(v.name.split(",")[0].split('[')[1]) 118 | c = int(v.name.split(",")[1].split(']')[0]) 119 | nurse_view[r, c]._value = next(iter(N[v])) 120 | 121 | return visualize_constraints(S, nurse_view, factory=factory, do_clear=False) 122 | 123 | --------------------------------------------------------------------------------