Skip to content

Commit be4ecb1

Browse files
author
Robbie Muir
committed
fix xpress
1 parent c5c004e commit be4ecb1

File tree

3 files changed

+58
-7
lines changed

3 files changed

+58
-7
lines changed

doc/release_notes.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Release Notes
22
=============
33

44
.. Upcoming Version
5-
5+
* xpress: Fixed for xpress v9.5 (broken since linopy v0.5.8), upgraded deprecated xpress methods
66
* Add support for SOS1 and SOS2 (Special Ordered Sets) constraints via ``Model.add_sos_constraints()`` and ``Model.remove_sos_constraints()``
77
* Add simplify method to LinearExpression to combine duplicate terms
88
* Add convenience function to create LinearExpression from constant

linopy/solvers.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,21 +1600,22 @@ def solve_problem_from_file(
16001600
except Exception as err:
16011601
logger.info("Unable to save solution file. Raised error: %s", err)
16021602

1603-
condition = m.getProbStatusString()
1603+
condition: str = m.getAttrib("solstatus").name.lower()
16041604
termination_condition = CONDITION_MAP.get(condition, condition)
16051605
status = Status.from_termination_condition(termination_condition)
16061606
status.legacy_status = condition
16071607

1608-
def get_solver_solution() -> Solution:
1609-
objective = m.getObjVal()
1608+
def get_solver_solution_new() -> Solution:
1609+
# For xpress >= 9.6
1610+
objective: float = m.getAttrib("objval")
16101611

1611-
var = m.getnamelist(xpress_Namespaces.COLUMN, 0, m.attributes.cols - 1)
1612+
var = m.getnamelist(xpress.Namespaces.COLUMN, 0, m.attributes.cols - 1)
16121613
sol = pd.Series(m.getSolution(), index=var, dtype=float)
16131614

16141615
try:
1615-
_dual = m.getDual()
1616+
_dual = m.getDuals()
16161617
constraints = m.getnamelist(
1617-
xpress_Namespaces.ROW, 0, m.attributes.rows - 1
1618+
xpress.Namespaces.ROW, 0, m.attributes.rows - 1
16181619
)
16191620
dual = pd.Series(_dual, index=constraints, dtype=float)
16201621
except (xpress.SolverError, xpress.ModelError, SystemError):
@@ -1623,6 +1624,29 @@ def get_solver_solution() -> Solution:
16231624

16241625
return Solution(sol, dual, objective)
16251626

1627+
def get_solver_solution_legacy() -> Solution:
1628+
# For xpress < 9.6
1629+
objective: float = m.getAttrib("objval")
1630+
1631+
var = [str(v) for v in m.getVariable()]
1632+
1633+
sol = pd.Series(m.getSolution(var), index=var, dtype=float)
1634+
1635+
try:
1636+
dual_ = [str(d) for d in m.getConstraint()]
1637+
dual = pd.Series(m.getDuals(dual_), index=dual_, dtype=float)
1638+
except (xpress.SolverError, xpress.ModelError, SystemError):
1639+
logger.warning("Dual values of MILP couldn't be parsed")
1640+
dual = pd.Series(dtype=float)
1641+
1642+
return Solution(sol, dual, objective)
1643+
1644+
def get_solver_solution() -> Solution:
1645+
if parse_version(xpress.__version__) >= parse_version("9.6"):
1646+
return get_solver_solution_new()
1647+
else:
1648+
return get_solver_solution_legacy()
1649+
16261650
solution = self.safe_get_solution(status=status, func=get_solver_solution)
16271651
solution = maybe_adjust_objective_sign(solution, io_api, sense)
16281652

test/test_optimization.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import itertools
1111
import logging
1212
from typing import Any
13+
from unittest.mock import patch
1314

1415
import numpy as np
1516
import pandas as pd
@@ -641,6 +642,32 @@ def test_model_with_inf(
641642
assert (model_with_inf.solution.y == 10).all()
642643

643644

645+
@pytest.mark.skipif(
646+
"xpress" not in available_solvers, reason="Xpress solver not available"
647+
)
648+
def test_old_xpress_version_no_dual(model_with_inf: Model) -> None:
649+
# Confirm that the old code path for xpress 9.5.x still works (when duals are not available)
650+
with patch("xpress.__version__", "9.5.0"):
651+
_, condition = model_with_inf.solve("xpress")
652+
assert condition == "optimal"
653+
assert (model_with_inf.solution.x == 0).all()
654+
assert (model_with_inf.solution.y == 10).all()
655+
assert len(model_with_inf.dual.keys()) == 0
656+
657+
658+
@pytest.mark.skipif(
659+
"xpress" not in available_solvers, reason="Xpress solver not available"
660+
)
661+
def test_old_xpress_version_with_dual(model: Model) -> None:
662+
# Confirm that the old code path for xpress 9.5.x still works (when duals are available)
663+
with patch("xpress.__version__", "9.5.0"):
664+
status, condition = model.solve("xpress")
665+
assert status == "ok"
666+
assert condition == "optimal"
667+
assert np.isclose(model.objective.value or 0, 3.3)
668+
assert np.isclose(model.dual["con0"].values, 0.3)
669+
670+
644671
@pytest.mark.parametrize(
645672
"solver,io_api,explicit_coordinate_names",
646673
[p for p in params if p[0] not in ["mindopt"]],

0 commit comments

Comments
 (0)