Skip to content

Commit

Permalink
fix variable types and move direction dual hacks into the MatrixProbl…
Browse files Browse the repository at this point in the history
…em class
  • Loading branch information
cdiener committed Sep 13, 2023
1 parent 9c1f970 commit 1524a32
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 25 deletions.
49 changes: 35 additions & 14 deletions src/optlang/matrix_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,9 @@ def rename_variable(self, old, new):
}
self.variable_lbs[new] = self.variable_lbs.pop(old)
self.variable_ubs[new] = self.variable_ubs.pop(old)
if old in self.integer_vars:
self.integer_vars.remove(old)
self.integer_vars.add(new)


class Variable(interface.Variable):
Expand All @@ -381,13 +384,24 @@ def type(self, value):
+ "The following variable types are available: "
+ ", ".join(_TYPES)
)
if value == "binary":
self.lb = 0
self.ub = 1
self.problem.problem.integer_vars.add(self.name)
elif value == "integer":
self.problem.problem.integer_vars.add(self.name)
elif value == "continuous":
if self.name in self.problem.problem.integer_vars:
self.problem.problem.integer_vars.remove(self.name)
super(Variable, Variable).type.fset(self, value)

def _get_primal(self):
return self.problem.problem.primals.get(self.name, None)

@property
def dual(self):
if self.problem.is_integer:
raise ValueError("Dual values are not well-defined for integer problems")
if self.problem is not None:
return self.problem.problem.vduals.get(self.name, None)
return None
Expand Down Expand Up @@ -462,11 +476,11 @@ def primal(self):

@property
def dual(self):
if self.problem.is_integer:
raise ValueError("Dual values are not well-defined for integer problems")
if self.problem is None:
return None
d = self.problem.problem.duals.get(self.name, None)
if d is not None:
d = -d
return d

@interface.Constraint.name.setter
Expand Down Expand Up @@ -582,6 +596,9 @@ def get_linear_coefficients(self, variables):


class Configuration(interface.MathematicalProgrammingConfiguration):
lp_methods = _LP_METHODS
qp_methods = _QP_METHODS

def __init__(
self,
lp_method="auto",
Expand All @@ -605,14 +622,14 @@ def __init__(
@property
def lp_method(self):
"""The algorithm used to solve LP problems."""
return "auto"
return self.problem.problem.settings["lp_method"]

@lp_method.setter
def lp_method(self, lp_method):
if lp_method not in _LP_METHODS:
if lp_method not in self.lp_methods:
raise ValueError(
"LP Method %s is not valid (choose one of: %s)"
% (lp_method, ", ".join(_LP_METHODS))
% (lp_method, ", ".join(self.lp_methods))
)

def _set_presolve(self, value):
Expand Down Expand Up @@ -679,14 +696,14 @@ def __setstate__(self, state):
@property
def qp_method(self):
"""Change the algorithm used to optimize QP problems."""
return "auto"
return self.problem.problem.settings["qp_method"]

@qp_method.setter
def qp_method(self, value):
if value not in _QP_METHODS:
raise ValueError(
"%s is not a valid qp_method. Choose between %s"
% (value, str(_QP_METHODS))
% (value, str(self.qp_methods))
)
self._qp_method = value

Expand All @@ -698,10 +715,10 @@ def _set_feasibility(self, value):
self.problem.problem.settings["dual_inf_tolerance"] = value

def _get_integrality(self):
return 1e-6
return self.problem.problem.settings["mip_tolerance"]

def _set_integrality(self, value):
pass
self.problem.problem.settings["mip_tolerance"] = value

def _get_optimality(self):
return self.problem.problem.settings["optimality_tolerance"]
Expand Down Expand Up @@ -782,7 +799,7 @@ def _initialize_model_from_problem(self, problem, vc_mapping=None, offset=0):
linear_expression + quadratic_expression + offset,
problem=self,
direction={-1: "max", 1: "min"}[self.problem.direction],
name="osqp_objective",
name="matrix_objective",
)

@property
Expand Down Expand Up @@ -835,21 +852,25 @@ def _get_primal_values(self):
return primal_values

def _get_reduced_costs(self):
if len(self.problem.duals) == 0:
if self.is_integer:
raise ValueError("Dual values are not well-defined for integer problems")
if len(self.problem.vduals) == 0:
raise SolverError("The problem has not been solved yet!")
reduced_costs = [self.problem.vduals[v.name] for v in self._variables]
return reduced_costs

def _get_constraint_values(self):
if len(self.problem.primals) == 0:
if len(self.problem.cprimals) == 0:
raise SolverError("The problem has not been solved yet!")
constraint_primals = [self.problem.cprimals[c.name] for c in self._constraints]
return constraint_primals

def _get_shadow_prices(self):
if len(self.problem.primals) == 0:
if self.is_integer:
raise ValueError("Dual values are not well-defined for integer problems")
if len(self.problem.duals) == 0:
raise SolverError("The problem has not been solved yet!")
dual_values = [-self.problem.duals[c.name] for c in self._constraints]
dual_values = [self.problem.duals[c.name] for c in self._constraints]
return dual_values

@property
Expand Down
18 changes: 7 additions & 11 deletions src/optlang/osqp_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@
Make sure that 'import osqp' runs without error.
"""
import logging
import six
import numpy as np

import optlang.matrix_interface as mi
from optlang import interface
from optlang.util import inheritdocstring

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -57,7 +55,7 @@
"maximum iterations reached": interface.ITERATION_LIMIT,
"unsolved": interface.SPECIAL,
"problem non convex": interface.SPECIAL,
"non-existing-status": "Here for testing that missing statuses are handled."
"non-existing-status": "Here for testing that missing statuses are handled.",
}


Expand Down Expand Up @@ -91,18 +89,14 @@ def osqp_settings(self):
def solve(self):
"""Solve the OSQP problem."""
settings = self.osqp_settings()
d = float(self.direction)
sp = self.build(add_variable_constraints=True)
solver = osqp.OSQP()
if sp.P is None:
# see https://github.com/cvxgrp/cvxpy/issues/898
settings.update({"adaptive_rho": 0, "rho": 1.0, "alpha": 1.0})
solver.setup(
P=sp.P,
q=sp.q,
A=sp.A,
l=sp.bounds[:, 0],
u=sp.bounds[:, 1],
**settings
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):
Expand All @@ -116,8 +110,10 @@ def solve(self):
self.primals = dict(zip(self.variables, solution.x))
self.vduals = dict(zip(self.variables, solution.y[nc : (nc + nv)]))
if nc > 0:
self.cprimals = dict(zip(self.constraints, sp.A.dot(solution.x)[0:nc]))
self.duals = dict(zip(self.constraints, solution.y[0:nc]))
self.cprimals = dict(
zip(self.constraints, sp.A.dot(solution.x)[0:nc] * d)
)
self.duals = dict(zip(self.constraints, solution.y[0:nc] * d))
if not np.isnan(solution.info.obj_val):
self.obj_value = solution.info.obj_val * self.direction
self.status = solution.info.status
Expand Down

0 comments on commit 1524a32

Please sign in to comment.