2424from random import random
2525from typing import Any , TypeAlias
2626
27- from mip import BINARY , Model , maximize , minimize , xsum
27+ import pulp
2828
2929from .utils import consecutive_ngrams , get_nested_key , sort_replacements
3030
@@ -55,16 +55,15 @@ def run_mip(
5555 )
5656 print ("\n \n \n " )
5757
58- if not replacements :
59- return [], 0.0 , {}
60-
61- mip_model = Model ()
58+ # Create pulp problem with appropriate sense (minimize or maximize)
59+ sense = pulp .LpMaximize if bigger_is_better else pulp .LpMinimize
60+ problem = pulp .LpProblem (name = "multi_layer_replacement" , sense = sense )
6261
6362 objective_vars = []
6463 constraint_vars = {constraint_key : [] for constraint_key in constraints }
6564 choice_indicators_by_layer = defaultdict (list )
66- for replacement_id , replacement in replacements .items ():
67- is_chosen = mip_model . add_var ( var_type = BINARY )
65+ for i , ( replacement_id , replacement ) in enumerate ( replacements .items () ):
66+ is_chosen = pulp . LpVariable ( f"choice_ { i } " , cat = pulp . LpBinary )
6867 replacement ["is_chosen" ] = is_chosen
6968
7069 for parent_layer_idx in replacement ["parent_layer_indices" ]:
@@ -79,30 +78,29 @@ def run_mip(
7978
8079 # MIP constraints: each parent layer must come from exactly one chosen replacement
8180 for parent_layer_idx , curr_choice_indicators in choice_indicators_by_layer .items ():
82- mip_model += xsum (curr_choice_indicators ) == 1
81+ problem += pulp . lpSum (curr_choice_indicators ) == 1
8382
8483 # MIP constraints: the sum of chosen replacement costs must be lower than the max cost
8584 for constraint_key , max_cost in constraints .items ():
8685 min_cost = None
8786 if isinstance (max_cost , Iterable ):
8887 min_cost , max_cost = max_cost
8988
90- if max_cost is not None :
91- mip_model += xsum (constraint_vars [constraint_key ]) <= max_cost
92- if min_cost is not None :
93- mip_model += xsum (constraint_vars [constraint_key ]) >= min_cost
89+ # PuLP is stricter than mip - it doesn't allow NaN/inf in constraints
90+ if max_cost is not None and math .isfinite (max_cost ):
91+ problem += pulp .lpSum (constraint_vars [constraint_key ]) <= max_cost
92+ if min_cost is not None and math .isfinite (min_cost ):
93+ problem += pulp .lpSum (constraint_vars [constraint_key ]) >= min_cost
9494
9595 # MIP objective
96- mip_model .objective = (
97- maximize (xsum (objective_vars )) if bigger_is_better else minimize (xsum (objective_vars ))
98- )
99-
100- if max_seconds_per_solution is not None :
101- mip_model .max_seconds = max_seconds_per_solution
96+ problem += (pulp .lpSum (objective_vars ), "objective" )
10297
103- mip_model .optimize ()
98+ # Configure and run solver
99+ solver = pulp .PULP_CBC_CMD (msg = True , timeLimit = max_seconds_per_solution )
100+ problem .solve (solver )
104101
105- if is_chosen .x is None :
102+ # Check if solution is feasible
103+ if problem .status != pulp .LpStatusOptimal :
106104 return []
107105 # raise InfeasibleError()
108106
@@ -112,7 +110,7 @@ def run_mip(
112110 chosen_replacements : ChosenReplacements = []
113111 chosen_layers = []
114112 for replacement_id , replacement in replacements .items ():
115- is_chosen = replacement ["is_chosen" ].x >= 0.99
113+ is_chosen = replacement ["is_chosen" ].varValue >= 0.99
116114 if is_chosen :
117115 assert replacement not in chosen_replacements
118116 chosen_replacements .append (replacement )
0 commit comments