2323from random import random
2424from typing import Any , Hashable , Iterable , Optional , TypeAlias
2525
26- from mip import BINARY , Model , maximize , minimize , xsum
26+ import pulp
2727
2828from modelopt .torch ._compress .mip .utils import (
2929 InfeasibleError ,
@@ -57,13 +57,15 @@ def run_mip(
5757 )
5858 print ("\n \n \n " )
5959
60- mip_model = Model ()
60+ # Create pulp problem with appropriate sense (minimize or maximize)
61+ sense = pulp .LpMaximize if bigger_is_better else pulp .LpMinimize
62+ problem = pulp .LpProblem (name = "multi_layer_replacement" , sense = sense )
6163
6264 objective_vars = []
6365 constraint_vars = {constraint_key : [] for constraint_key in constraints .keys ()}
6466 choice_indicators_by_layer = defaultdict (list )
65- for replacement_id , replacement in replacements .items ():
66- is_chosen = mip_model . add_var ( var_type = BINARY )
67+ for i , ( replacement_id , replacement ) in enumerate ( replacements .items () ):
68+ is_chosen = pulp . LpVariable ( f"choice_ { i } " , cat = pulp . LpBinary )
6769 replacement ["is_chosen" ] = is_chosen
6870
6971 for parent_layer_idx in replacement ["parent_layer_indices" ]:
@@ -78,30 +80,29 @@ def run_mip(
7880
7981 # MIP constraints: each parent layer must come from exactly one chosen replacement
8082 for parent_layer_idx , curr_choice_indicators in choice_indicators_by_layer .items ():
81- mip_model += xsum (curr_choice_indicators ) == 1
83+ problem += pulp . lpSum (curr_choice_indicators ) == 1
8284
8385 # MIP constraints: the sum of chosen replacement costs must be lower than the max cost
8486 for constraint_key , max_cost in constraints .items ():
8587 min_cost = None
8688 if isinstance (max_cost , Iterable ):
8789 min_cost , max_cost = max_cost
8890
89- if max_cost is not None :
90- mip_model += xsum (constraint_vars [constraint_key ]) <= max_cost
91- if min_cost is not None :
92- mip_model += xsum (constraint_vars [constraint_key ]) >= min_cost
91+ # PuLP is stricter than mip - it doesn't allow NaN/inf in constraints
92+ if max_cost is not None and math .isfinite (max_cost ):
93+ problem += pulp .lpSum (constraint_vars [constraint_key ]) <= max_cost
94+ if min_cost is not None and math .isfinite (min_cost ):
95+ problem += pulp .lpSum (constraint_vars [constraint_key ]) >= min_cost
9396
9497 # MIP objective
95- mip_model .objective = (
96- maximize (xsum (objective_vars )) if bigger_is_better else minimize (xsum (objective_vars ))
97- )
98-
99- if max_seconds_per_solution is not None :
100- mip_model .max_seconds = max_seconds_per_solution
98+ problem += (pulp .lpSum (objective_vars ), "objective" )
10199
102- mip_model .optimize ()
100+ # Configure and run solver
101+ solver = pulp .PULP_CBC_CMD (msg = True , timeLimit = max_seconds_per_solution )
102+ problem .solve (solver )
103103
104- if is_chosen .x is None :
104+ # Check if solution is feasible
105+ if problem .status != pulp .LpStatusOptimal :
105106 return []
106107 # raise InfeasibleError()
107108
@@ -111,7 +112,7 @@ def run_mip(
111112 chosen_replacements : ChosenReplacements = []
112113 chosen_layers = []
113114 for replacement_id , replacement in replacements .items ():
114- is_chosen = replacement ["is_chosen" ].x >= 0.99
115+ is_chosen = replacement ["is_chosen" ].varValue >= 0.99
115116 if is_chosen :
116117 assert replacement not in chosen_replacements
117118 chosen_replacements .append (replacement )
0 commit comments