@@ -65,7 +65,6 @@ def sequence_constraints_with_circuit(
6565 all_tasks = range (num_tasks )
6666
6767 arcs : List [cp_model .ArcT ] = []
68- penalty_terms = []
6968 for i in all_tasks :
7069 # if node i is first.
7170 start_lit = model .new_bool_var (f"start_{ i } " )
@@ -85,17 +84,6 @@ def sequence_constraints_with_circuit(
8584 type_length_min = sequence_length_constraints [task_types [i ]][0 ]
8685 model .add (lengths [i ] >= type_length_min ).only_enforce_if (end_lit )
8786
88- # Penalize the cumul of the last task w.r.t. the soft max
89- soft_max , linear_penalty , hard_max = sequence_cumul_constraints [task_types [i ]]
90- if soft_max < hard_max :
91- aux = model .new_int_var (0 , hard_max - soft_max , f"aux_{ i } " )
92- model .add_max_equality (aux , [0 , cumuls [i ] - soft_max ])
93-
94- excess = model .new_int_var (0 , hard_max - soft_max , f"excess_{ i } " )
95- model .add (excess == aux ).only_enforce_if (end_lit )
96- model .add (excess == 0 ).only_enforce_if (~ end_lit )
97- penalty_terms .append ((excess , linear_penalty ))
98-
9987 for j in all_tasks :
10088 if i == j :
10189 continue
@@ -135,19 +123,25 @@ def sequence_constraints_with_circuit(
135123 # Reset the cumul to the duration of the task.
136124 model .add (cumuls [j ] == durations [j ]).only_enforce_if (lit )
137125
138- # Penalize the cumul of the previous task w.r.t. the soft max
139- if soft_max < hard_max :
140- aux = model .new_int_var (0 , hard_max - soft_max , f"aux_{ i } " )
141- model .add_max_equality (aux , [0 , cumuls [i ] - soft_max ])
142-
143- excess = model .new_int_var (0 , hard_max - soft_max , f"excess_{ i } " )
144- model .add (excess == aux ).only_enforce_if (lit )
145- model .add (excess == 0 ).only_enforce_if (~ lit )
146- penalty_terms .append ((excess , linear_penalty ))
147-
148126 # Add the circuit constraint.
149127 model .add_circuit (arcs )
150128
129+ # Create the penalty terms. We can penalize each cumul locally.
130+ penalty_terms = []
131+ for i in all_tasks :
132+ # Penalize the cumul of the last task w.r.t. the soft max
133+ soft_max , linear_penalty , hard_max = sequence_cumul_constraints [task_types [i ]]
134+
135+ # To make it separable per task, and avoid double counting, we use the
136+ # following trick:
137+ # reduced_excess = min(durations[i], max(0, cumul[i] - soft_max))
138+ if soft_max < hard_max :
139+ excess = model .new_int_var (0 , hard_max - soft_max , f"excess+_{ i } " )
140+ model .add_max_equality (excess , [0 , cumuls [i ] - soft_max ])
141+ reduced_excess = model .new_int_var (0 , durations [i ], f"reduced_excess_{ i } " )
142+ model .add_min_equality (reduced_excess , [durations [i ], excess ])
143+ penalty_terms .append ((reduced_excess , linear_penalty ))
144+
151145 return penalty_terms
152146
153147
@@ -215,13 +209,13 @@ def sequences_in_no_overlap_sample_sat():
215209 lengths = []
216210 for i in all_tasks :
217211 max_hard_length = sequence_length_constraints [task_types [i ]][1 ]
218- lengths .append (model .new_int_var (0 , max_hard_length , f"length_{ i } " ))
212+ lengths .append (model .new_int_var (1 , max_hard_length , f"length_{ i } " ))
219213
220214 # Create cumul variables for each task.
221215 cumuls = []
222216 for i in all_tasks :
223217 max_hard_cumul = sequence_cumul_constraints [task_types [i ]][2 ]
224- cumuls .append (model .new_int_var (0 , max_hard_cumul , f"cumul_{ i } " ))
218+ cumuls .append (model .new_int_var (durations [ i ] , max_hard_cumul , f"cumul_{ i } " ))
225219
226220 # Adds NoOverlap constraint.
227221 model .add_no_overlap (intervals )
@@ -244,6 +238,7 @@ def sequences_in_no_overlap_sample_sat():
244238
245239 # Solves the model model.
246240 solver = cp_model .CpSolver ()
241+ solver .parameters .log_search_progress = True
247242 status = solver .solve (model )
248243
249244 if status == cp_model .OPTIMAL or status == cp_model .FEASIBLE :
0 commit comments