Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Release Notes
=============

.. Upcoming Version

* xpress: Fixed for xpress v9.5 (broken since linopy v0.5.8), upgraded deprecated xpress methods
* Add support for SOS1 and SOS2 (Special Ordered Sets) constraints via ``Model.add_sos_constraints()`` and ``Model.remove_sos_constraints()``
* Add simplify method to LinearExpression to combine duplicate terms
* Add convenience function to create LinearExpression from constant
Expand Down
36 changes: 30 additions & 6 deletions linopy/solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1600,21 +1600,22 @@ def solve_problem_from_file(
except Exception as err:
logger.info("Unable to save solution file. Raised error: %s", err)

condition = m.getProbStatusString()
condition: str = m.getAttrib("solstatus").name.lower()
termination_condition = CONDITION_MAP.get(condition, condition)
status = Status.from_termination_condition(termination_condition)
status.legacy_status = condition

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

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

try:
_dual = m.getDual()
_dual = m.getDuals()
constraints = m.getnamelist(
xpress_Namespaces.ROW, 0, m.attributes.rows - 1
xpress.Namespaces.ROW, 0, m.attributes.rows - 1
)
dual = pd.Series(_dual, index=constraints, dtype=float)
except (xpress.SolverError, xpress.ModelError, SystemError):
Expand All @@ -1623,6 +1624,29 @@ def get_solver_solution() -> Solution:

return Solution(sol, dual, objective)

def get_solver_solution_legacy() -> Solution:
# For xpress < 9.6
objective: float = m.getAttrib("objval")

var = [str(v) for v in m.getVariable()]

sol = pd.Series(m.getSolution(var), index=var, dtype=float)

try:
dual_ = [str(d) for d in m.getConstraint()]
dual = pd.Series(m.getDuals(dual_), index=dual_, dtype=float)
except (xpress.SolverError, xpress.ModelError, SystemError):
logger.warning("Dual values of MILP couldn't be parsed")
dual = pd.Series(dtype=float)

return Solution(sol, dual, objective)

def get_solver_solution() -> Solution:
if parse_version(xpress.__version__) >= parse_version("9.6"):
return get_solver_solution_new()
else:
return get_solver_solution_legacy()

solution = self.safe_get_solution(status=status, func=get_solver_solution)
solution = maybe_adjust_objective_sign(solution, io_api, sense)

Expand Down
27 changes: 27 additions & 0 deletions test/test_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import itertools
import logging
from typing import Any
from unittest.mock import patch

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -641,6 +642,32 @@ def test_model_with_inf(
assert (model_with_inf.solution.y == 10).all()


@pytest.mark.skipif(
"xpress" not in available_solvers, reason="Xpress solver not available"
)
def test_old_xpress_version_no_dual(model_with_inf: Model) -> None:
# Confirm that the old code path for xpress 9.5.x still works (when duals are not available)
with patch("xpress.__version__", "9.5.0"):
_, condition = model_with_inf.solve("xpress")
assert condition == "optimal"
assert (model_with_inf.solution.x == 0).all()
assert (model_with_inf.solution.y == 10).all()
assert len(model_with_inf.dual.keys()) == 0


@pytest.mark.skipif(
"xpress" not in available_solvers, reason="Xpress solver not available"
)
def test_old_xpress_version_with_dual(model: Model) -> None:
# Confirm that the old code path for xpress 9.5.x still works (when duals are available)
with patch("xpress.__version__", "9.5.0"):
status, condition = model.solve("xpress")
assert status == "ok"
assert condition == "optimal"
assert np.isclose(model.objective.value or 0, 3.3)
assert np.isclose(model.dual["con0"].values, 0.3)


@pytest.mark.parametrize(
"solver,io_api,explicit_coordinate_names",
[p for p in params if p[0] not in ["mindopt"]],
Expand Down