Skip to content

Commit

Permalink
Fix/hybrid valid (#260)
Browse files Browse the repository at this point in the history
* fix check for valid solution

* use right setting for tolerance

* fix the feasibility check

* add the hybrid solver to the engine selection
  • Loading branch information
cdiener authored Apr 24, 2024
1 parent 92517c4 commit 8cc7cfd
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 11 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
Next Release
-----

1.8.2
-----
* fix the feasibility check in the hybrid solver

1.8.1
-----
* update versioneer to support newer Python versions

1.8.0
-----
* add a generic matrix interface to allow easy addition of new solvers
Expand Down
2 changes: 1 addition & 1 deletion src/optlang/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@

# Go through and find the best solver that loaded. Load that one as the default
for engine_str in ['cplex_interface', 'gurobi_interface', 'glpk_interface',
'scipy_interface', 'coinor_cbc_interface']:
'hybrid_interface', 'scipy_interface', 'coinor_cbc_interface']:
# Must check globals since not all interface variables will be defined
if engine_str in globals():
engine = globals()[engine_str]
Expand Down
23 changes: 14 additions & 9 deletions src/optlang/hybrid_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,14 +153,16 @@ def solve_osqp(self):
d = float(self.direction)
sp = self.build(add_variable_constraints=True)
solver = osqp.OSQP()
log.debug("Setting up OSQP problem.")
solver.setup(
P=sp.P, q=sp.q, A=sp.A, l=sp.bounds[:, 0], u=sp.bounds[:, 1], **settings
)
if self._solution is not None:
if self.still_valid(sp.A, sp.bounds):
if self.still_valid(sp):
solver.warm_start(x=self._solution["x"], y=self._solution["y"])
if "rho" in self._solution:
solver.update_settings(rho=self._solution["rho"])
log.debug("Starting OSQP solve.")
solution = solver.solve()
nc = len(self.constraints)
nv = len(self.variables)
Expand Down Expand Up @@ -188,6 +190,7 @@ def solve_highs(self):
d = float(self.direction)
options = self.highs_settings()
sp = self.build()
log.debug("Setting up HIGHS problem.")
env = self._highs_env
model = hs.HighsModel()
env.passOptions(options)
Expand All @@ -208,6 +211,7 @@ def solve_highs(self):
if len(self.integer_vars) > 0:
model.lp_.integrality_ = HIGHS_VAR_TYPES[sp.integer]
env.passModel(model)
log.debug("Starting HIGHS solve.")
env.run()
info = env.getInfo()
self.status = env.modelStatusToString(env.getModelStatus())
Expand Down Expand Up @@ -236,18 +240,19 @@ def solve(self):
else:
self.solve_osqp()

def still_valid(self, A, bounds):
def still_valid(self, problem):
"""Check if previous solutions is still feasible."""
if len(self._solution["x"]) != len(self.variables) or len(
nv, nc = len(self.variables), len(self.constraints)
b = problem.bounds
if len(self._solution["x"]) != nv or len(
self._solution["y"]
) != len(self.constraints):
) != nc + nv:
return False
c = A.dot(self._solution["x"])
ea = self.settings["eps_abs"]
er = self.settings["eps_rel"]
c = problem.A.dot(self._solution["x"])
tol = self.settings["primal_inf_tolerance"]
valid = np.all(
(c + er * np.abs(c) + ea >= bounds[:, 0])
& (c - er * np.abs(c) - ea <= bounds[:, 1])
(c + tol * np.abs(c) + tol >= b[:, 0])
& (c - tol * np.abs(c) - tol <= b[:, 1])
)
return valid

Expand Down
2 changes: 1 addition & 1 deletion src/optlang/matrix_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def build(self, add_variable_constraints=False):
bounds = concatenate((bounds, vbounds))
A = csc_matrix(
(A[:, 2], (A[:, 0].astype("int64"), A[:, 1].astype("int64"))),
shape=(nc + nv, nv),
shape=(bounds.shape[0], nv),
)
elif add_variable_constraints:
Av = array([[vmap[k], vmap[k], 1.0] for k in self.variables])
Expand Down
7 changes: 7 additions & 0 deletions src/optlang/tests/test_hybrid_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,13 @@ def test_hybrid_change_objective_can_handle_removed_vars(self):
self.assertEqual(self.model.problem.obj_linear_coefs,
{self.model.variables[2].name: 1.0})

def test_check_for_valid_solution(self):
self.model.optimize()
form = self.model.problem.build(True)
self.assertTrue(self.model.problem.still_valid(form))
form = self.model.problem.build(False)
self.assertTrue(self.model.problem.still_valid(form))


class ConfigurationTestCase(abstract_test_cases.AbstractConfigurationTestCase):

Expand Down

0 comments on commit 8cc7cfd

Please sign in to comment.