@@ -1283,24 +1283,65 @@ def compute_infeasibilities(self) -> list[int]:
12831283 """
12841284 Compute a set of infeasible constraints.
12851285
1286- This function requires that the model was solved with `gurobi` and the
1287- termination condition was infeasible.
1286+ This function requires that the model was solved with `gurobi` or `xpress`
1287+ and the termination condition was infeasible. The solver must have detected
1288+ the infeasibility during the solve process.
12881289
12891290 Returns
12901291 -------
12911292 labels : list[int]
12921293 Labels of the infeasible constraints.
12931294 """
1294- if "gurobi" not in available_solvers :
1295- raise ImportError ("Gurobi is required for this method." )
1295+ solver_model = getattr (self , "solver_model" , None )
12961296
1297- import gurobipy
1297+ # Check for Gurobi
1298+ if "gurobi" in available_solvers :
1299+ try :
1300+ import gurobipy
12981301
1299- solver_model = getattr (self , "solver_model" )
1302+ if solver_model is not None and isinstance (
1303+ solver_model , gurobipy .Model
1304+ ):
1305+ return self ._compute_infeasibilities_gurobi (solver_model )
1306+ except ImportError :
1307+ pass
13001308
1301- if not isinstance (solver_model , gurobipy .Model ):
1302- raise NotImplementedError ("Solver model must be a Gurobi Model." )
1309+ # Check for Xpress
1310+ if "xpress" in available_solvers :
1311+ try :
1312+ import xpress
1313+
1314+ if solver_model is not None and isinstance (
1315+ solver_model , xpress .problem
1316+ ):
1317+ return self ._compute_infeasibilities_xpress (solver_model )
1318+ except ImportError :
1319+ pass
1320+
1321+ # If we get here, either the solver doesn't support IIS or no solver model is available
1322+ if solver_model is None :
1323+ # Check if this is a supported solver without a stored model
1324+ solver_name = getattr (self , "solver_name" , "unknown" )
1325+ if solver_name in ["gurobi" , "xpress" ]:
1326+ raise ValueError (
1327+ "No solver model available. The model must be solved first with "
1328+ "'gurobi' or 'xpress' solver and the result must be infeasible."
1329+ )
1330+ else :
1331+ # This is an unsupported solver
1332+ raise NotImplementedError (
1333+ f"Computing infeasibilities is not supported for '{ solver_name } ' solver. "
1334+ "Only Gurobi and Xpress solvers support IIS computation."
1335+ )
1336+ else :
1337+ # We have a solver model but it's not a supported type
1338+ raise NotImplementedError (
1339+ "Computing infeasibilities is only supported for Gurobi and Xpress solvers. "
1340+ f"Current solver model type: { type (solver_model ).__name__ } "
1341+ )
13031342
1343+ def _compute_infeasibilities_gurobi (self , solver_model : Any ) -> list [int ]:
1344+ """Compute infeasibilities for Gurobi solver."""
13041345 solver_model .computeIIS ()
13051346 f = NamedTemporaryFile (suffix = ".ilp" , prefix = "linopy-iis-" , delete = False )
13061347 solver_model .write (f .name )
@@ -1315,13 +1356,86 @@ def compute_infeasibilities(self) -> list[int]:
13151356 match = pattern .match (line_decoded )
13161357 if match :
13171358 labels .append (int (match .group (1 )))
1359+ f .close ()
13181360 return labels
13191361
1362+ def _compute_infeasibilities_xpress (self , solver_model : Any ) -> list [int ]:
1363+ """Compute infeasibilities for Xpress solver."""
1364+ # Compute all IIS
1365+ solver_model .iisall ()
1366+
1367+ # Get the number of IIS found
1368+ num_iis = solver_model .attributes .numiis
1369+ if num_iis == 0 :
1370+ return []
1371+
1372+ labels = set ()
1373+
1374+ # Create constraint mapping for efficient lookups
1375+ constraint_to_index = {
1376+ constraint : idx
1377+ for idx , constraint in enumerate (solver_model .getConstraint ())
1378+ }
1379+
1380+ # Retrieve each IIS
1381+ for iis_num in range (1 , num_iis + 1 ):
1382+ iis_constraints = self ._extract_iis_constraints (solver_model , iis_num )
1383+
1384+ # Convert constraint objects to indices
1385+ for constraint_obj in iis_constraints :
1386+ if constraint_obj in constraint_to_index :
1387+ labels .add (constraint_to_index [constraint_obj ])
1388+ # Note: Silently skip constraints not found in mapping
1389+ # This can happen if the model structure changed after solving
1390+
1391+ return sorted (list (labels ))
1392+
1393+ def _extract_iis_constraints (self , solver_model : Any , iis_num : int ) -> list [Any ]:
1394+ """
1395+ Extract constraint objects from a specific IIS.
1396+
1397+ Parameters
1398+ ----------
1399+ solver_model : xpress.problem
1400+ The Xpress solver model
1401+ iis_num : int
1402+ IIS number (1-indexed)
1403+
1404+ Returns
1405+ -------
1406+ list[Any]
1407+ List of xpress.constraint objects in the IIS
1408+ """
1409+ # Prepare lists to receive IIS data
1410+ miisrow : list [Any ] = [] # xpress.constraint objects in the IIS
1411+ miiscol : list [Any ] = [] # xpress.variable objects in the IIS
1412+ constrainttype : list [str ] = [] # Constraint types ('L', 'G', 'E')
1413+ colbndtype : list [str ] = [] # Column bound types
1414+ duals : list [float ] = [] # Dual values
1415+ rdcs : list [float ] = [] # Reduced costs
1416+ isolationrows : list [str ] = [] # Row isolation info
1417+ isolationcols : list [str ] = [] # Column isolation info
1418+
1419+ # Get IIS data from Xpress
1420+ solver_model .getiisdata (
1421+ iis_num ,
1422+ miisrow ,
1423+ miiscol ,
1424+ constrainttype ,
1425+ colbndtype ,
1426+ duals ,
1427+ rdcs ,
1428+ isolationrows ,
1429+ isolationcols ,
1430+ )
1431+
1432+ return miisrow
1433+
13201434 def print_infeasibilities (self , display_max_terms : int | None = None ) -> None :
13211435 """
13221436 Print a list of infeasible constraints.
13231437
1324- This function requires that the model was solved using `gurobi`
1438+ This function requires that the model was solved using `gurobi` or `xpress`
13251439 and the termination condition was infeasible.
13261440
13271441 Parameters
@@ -1346,7 +1460,7 @@ def compute_set_of_infeasible_constraints(self) -> Dataset:
13461460 """
13471461 Compute a set of infeasible constraints.
13481462
1349- This function requires that the model was solved with `gurobi` and the
1463+ This function requires that the model was solved with `gurobi` or `xpress` and the
13501464 termination condition was infeasible.
13511465
13521466 Returns
0 commit comments