diff --git a/src/ppopt/solver.py b/src/ppopt/solver.py index a8e0bd3..7321d94 100644 --- a/src/ppopt/solver.py +++ b/src/ppopt/solver.py @@ -125,11 +125,11 @@ def check_supported_problem(self, problem_name: str) -> None: raise RuntimeError(message) # noinspection PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList - def solve_miqp(self, Q: Optional[numpy.ndarray], c: Optional[numpy.ndarray], A: Optional[numpy.ndarray], - b: Optional[numpy.ndarray], - equality_constraints: Iterable[int] = None, - bin_vars: Iterable[int] = None, verbose: bool = False, - get_duals: bool = True) -> Optional[SolverOutput]: + def solve_miqp(self, Q: Optional[numpy.ndarray], c: Optional[numpy.ndarray], + A: Optional[numpy.ndarray], b: Optional[numpy.ndarray], + equality_constraints: Iterable[int] = None, bin_vars: Iterable[int] = None, + verbose: bool = False, get_duals: bool = True, get_status: bool = False)\ + -> Optional[SolverOutput]: r""" This is the breakout for solving mixed integer quadratic programs @@ -154,20 +154,25 @@ def solve_miqp(self, Q: Optional[numpy.ndarray], c: Optional[numpy.ndarray], A: :param equality_constraints: List of Equality constraints :param bin_vars: List of binary variable indices :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False, only + implemented for gurobi - :return: A SolverOutput object if optima found, otherwise None. + :return: A SolverOutput object, or None if get_status is false and problem is + infeasible or unbounded """ if self.solvers['miqp'] == "gurobi": - return solve_miqp_gurobi(Q, c, A, b, equality_constraints, bin_vars, verbose, get_duals) + return solve_miqp_gurobi(Q, c, A, b, equality_constraints, bin_vars, + verbose, get_duals, get_status) return self.solver_not_supported(self.solvers['miqp']) - def solve_qp(self, Q: Optional[numpy.ndarray], c: Optional[numpy.ndarray], A: Optional[numpy.ndarray], - b: Optional[numpy.ndarray], equality_constraints: Iterable[int] = None, - verbose=False, - get_duals=True) -> Optional[SolverOutput]: + def solve_qp(self, Q: Optional[numpy.ndarray], c: Optional[numpy.ndarray], + A: Optional[numpy.ndarray], b: Optional[numpy.ndarray], + equality_constraints: Iterable[int] = None, verbose=False, + get_duals=True, get_status: bool = False) -> Optional[SolverOutput]: r""" This is the breakout for solving quadratic programs @@ -190,13 +195,18 @@ def solve_qp(self, Q: Optional[numpy.ndarray], c: Optional[numpy.ndarray], A: Op :param b: Constraint RHS matrix, can be None :param equality_constraints: List of Equality constraints :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False, only + implemented for gurobi - :return: A SolverOutput object if optima found, otherwise None. + :return: A SolverOutput object, or None if get_status is false and problem is + infeasible or unbounded """ if self.solvers['qp'] == "gurobi": - return solve_qp_gurobi(Q, c, A, b, equality_constraints, verbose, get_duals) + return solve_qp_gurobi(Q, c, A, b, equality_constraints, + verbose, get_duals, get_status) if self.solvers['qp'] == "quadprog": return solve_qp_quadprog(Q, c, A, b, equality_constraints, verbose, get_duals) @@ -204,9 +214,9 @@ def solve_qp(self, Q: Optional[numpy.ndarray], c: Optional[numpy.ndarray], A: Op return self.solver_not_supported(self.solvers['qp']) # noinspection PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList - def solve_lp(self, c: Optional[numpy.ndarray], A: Optional[numpy.ndarray], b: Optional[numpy.ndarray], - equality_constraints=None, verbose=False, - get_duals=True) -> Optional[SolverOutput]: + def solve_lp(self, c: Optional[numpy.ndarray], A: Optional[numpy.ndarray], + b: Optional[numpy.ndarray], equality_constraints=None, verbose=False, + get_duals=True, get_status: bool = False) -> Optional[SolverOutput]: r""" This is the breakout for solving linear programs @@ -228,22 +238,28 @@ def solve_lp(self, c: Optional[numpy.ndarray], A: Optional[numpy.ndarray], b: Op :param b: Constraint RHS matrix, can be None :param equality_constraints: List of Equality constraints :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False, only + implemented for gurobi - :return: A SolverOutput object if optima found, otherwise None. + :return: A SolverOutput object, or None if get_status is false and problem is + infeasible or unbounded """ if self.solvers['lp'] == "gurobi": - return solve_lp_gurobi(c, A, b, equality_constraints, verbose, get_duals) + return solve_lp_gurobi(c, A, b, equality_constraints, + verbose, get_duals, get_status) if self.solvers['lp'] == 'glpk': return solve_lp_cvxopt(c, A, b, equality_constraints, verbose, get_duals) return self.solver_not_supported(self.solvers['lp']) - def solve_milp(self, c: Optional[numpy.ndarray], A: Optional[numpy.ndarray], b: Optional[numpy.ndarray], - equality_constraints: Iterable[int] = None, - bin_vars: Iterable[int] = None, verbose=False, get_duals=True) -> Optional[SolverOutput]: + def solve_milp(self, c: Optional[numpy.ndarray], A: Optional[numpy.ndarray], + b: Optional[numpy.ndarray], equality_constraints: Iterable[int] = None, + bin_vars: Iterable[int] = None, verbose=False, + get_duals=True, get_status: bool = False) -> Optional[SolverOutput]: r""" This is the breakout for solving mixed integer linear programs @@ -267,12 +283,17 @@ def solve_milp(self, c: Optional[numpy.ndarray], A: Optional[numpy.ndarray], b: :param equality_constraints: List of Equality constraints :param bin_vars: List of binary variable indices :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False, only + implemented for gurobi - :return: A dictionary of the Solver outputs, or none if infeasible or unbounded. output['sol'] = primal variables, output['dual'] = dual variables, output['obj'] = objective value, output['const'] = slacks, output['active'] = active constraints. + :return: A SolverOutput object, or None if get_status is false and problem is + infeasible or unbounded """ if self.solvers['milp'] == "gurobi": - return solve_milp_gurobi(c, A, b, equality_constraints, bin_vars, verbose, get_duals) + return solve_milp_gurobi(c, A, b, equality_constraints, bin_vars, + verbose, get_duals, get_status) return self.solver_not_supported(self.solvers['milp']) diff --git a/src/ppopt/solver_interface/gurobi_solver_interface.py b/src/ppopt/solver_interface/gurobi_solver_interface.py index be43402..c58b7e8 100644 --- a/src/ppopt/solver_interface/gurobi_solver_interface.py +++ b/src/ppopt/solver_interface/gurobi_solver_interface.py @@ -14,11 +14,11 @@ ) -def solve_miqp_gurobi(Q: numpy.ndarray = None, c: numpy.ndarray = None, A: numpy.ndarray = None, - b: numpy.ndarray = None, - equality_constraints: Iterable[int] = None, - bin_vars: Iterable[int] = None, verbose: bool = False, - get_duals: bool = True) -> Optional[SolverOutput]: +def solve_miqp_gurobi(Q: numpy.ndarray = None, c: numpy.ndarray = None, + A: numpy.ndarray = None, b: numpy.ndarray = None, + equality_constraints: Iterable[int] = None, bin_vars: Iterable[int] = None, + verbose: bool = False, get_duals: bool = True, get_status: bool = False)\ + -> Optional[SolverOutput]: r""" This is the breakout for solving mixed integer quadratic programs with gruobi @@ -43,9 +43,12 @@ def solve_miqp_gurobi(Q: numpy.ndarray = None, c: numpy.ndarray = None, A: numpy :param equality_constraints: List of Equality constraints :param bin_vars: List of binary variable indices :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False - :return: A solver object relating to the solution of the optimization problem + :return: A SolverOutput object, or none if get_status is false and problem is + infeasible or unbounded """ model = gp.Model() @@ -115,11 +118,22 @@ def solve_miqp_gurobi(Q: numpy.ndarray = None, c: numpy.ndarray = None, A: numpy status = model.status # if not solved return None if status != GRB.OPTIMAL and status != GRB.SUBOPTIMAL: - return None + if get_status: + if status == GRB.INF_OR_UNBD: + model.Params.dualReductions = 0 + model.optimize() + model.update() + + return SolverOutput(numpy.nan, numpy.full((num_vars,), numpy.nan, float), + numpy.full((num_constraints,), numpy.nan, float), numpy.empty((0,), int), + numpy.full((num_constraints,), numpy.nan, float), model.status) + + else: + return None # create the Solver return object - sol = SolverOutput(obj=model.getAttr("ObjVal"), sol=numpy.array(x.X), slack=None, - active_set=None, dual=None) + sol = SolverOutput(obj=model.getAttr("ObjVal"), sol=numpy.array(x.X), + slack=None, active_set=None, dual=None, status=status) # if we have a constrained system we need to add in the slack variables and active set if num_constraints != 0: @@ -135,10 +149,10 @@ def solve_miqp_gurobi(Q: numpy.ndarray = None, c: numpy.ndarray = None, A: numpy return sol -def solve_qp_gurobi(Q: numpy.ndarray, c: numpy.ndarray, A: numpy.ndarray, b: numpy.ndarray, - equality_constraints: Iterable[int] = None, - verbose=False, - get_duals=True) -> Optional[SolverOutput]: +def solve_qp_gurobi(Q: numpy.ndarray, c: numpy.ndarray, + A: numpy.ndarray, b: numpy.ndarray, equality_constraints: Iterable[int] = None, + verbose=False, get_duals=True, get_status: bool = False)\ + -> Optional[SolverOutput]: r""" This is the breakout for solving quadratic programs with gruobi @@ -159,18 +173,22 @@ def solve_qp_gurobi(Q: numpy.ndarray, c: numpy.ndarray, A: numpy.ndarray, b: num :param b: Constraint RHS matrix, can be None :param equality_constraints: List of Equality constraints :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False - :return: A SolverOutput Object + :return: A SolverOutput object, or none if get_status is false and problem is + infeasible or unbounded """ - return solve_miqp_gurobi(Q=Q, c=c, A=A, b=b, equality_constraints=equality_constraints, verbose=verbose, - get_duals=get_duals) + return solve_miqp_gurobi(Q=Q, c=c, A=A, b=b, + equality_constraints=equality_constraints, + verbose=verbose, get_duals=get_duals, get_status=get_status) # noinspection PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList -def solve_lp_gurobi(c: numpy.ndarray, A: numpy.ndarray, b: numpy.ndarray, equality_constraints: Iterable[int] = None, - verbose: bool = False, - get_duals: bool = True) -> Optional[SolverOutput]: +def solve_lp_gurobi(c: numpy.ndarray, A: numpy.ndarray, b: numpy.ndarray, + equality_constraints: Iterable[int] = None, verbose: bool = False, + get_duals: bool = True, get_status: bool = False) -> Optional[SolverOutput]: r""" This is the breakout for solving linear programs with gruobi. @@ -191,19 +209,23 @@ def solve_lp_gurobi(c: numpy.ndarray, A: numpy.ndarray, b: numpy.ndarray, equali :param equality_constraints: List of Equality constraints :param verbose: Flag for output of underlying Solver, default False :param get_duals: Flag for returning dual variable of problem, default True + :param get_status: Flag for returning gurobi solver status, default False - :return: A SolverOutput Object + :return: A SolverOutput object, or none if get_status is false and problem is + infeasible or unbounded """ if not gurobi_pretest(A, b): return None - return solve_miqp_gurobi(c=c, A=A, b=b, equality_constraints=equality_constraints, verbose=verbose, - get_duals=get_duals) + return solve_miqp_gurobi(c=c, A=A, b=b, + equality_constraints=equality_constraints, + verbose=verbose, get_duals=get_duals, get_status=get_status) def solve_milp_gurobi(c: numpy.ndarray, A: numpy.ndarray, b: numpy.ndarray, - equality_constraints: Iterable[int] = None, - bin_vars: Iterable[int] = None, verbose=False, get_duals=True) -> Optional[SolverOutput]: + equality_constraints: Iterable[int] = None, bin_vars: Iterable[int] = None, + verbose=False, get_duals=True, get_status: bool = False)\ + -> Optional[SolverOutput]: r""" This is the breakout for solving mixed integer linear programs with gruobi, This is feed directly into the MIQP Solver that is defined in the same file. @@ -227,15 +249,19 @@ def solve_milp_gurobi(c: numpy.ndarray, A: numpy.ndarray, b: numpy.ndarray, :param equality_constraints: List of Equality constraints :param bin_vars: List of binary variable indices :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False - :return: A SolverOutput Object + :return: A SolverOutput object, or none if get_status is false and problem is + infeasible or unbounded """ if not gurobi_pretest(A, b): return None - return solve_miqp_gurobi(c=c, A=A, b=b, equality_constraints=equality_constraints, bin_vars=bin_vars, - verbose=verbose, get_duals=get_duals) + return solve_miqp_gurobi(c=c, A=A, b=b, + equality_constraints=equality_constraints, bin_vars=bin_vars, + verbose=verbose, get_duals=get_duals, get_status=get_status) def gurobi_pretest(A, b) -> bool: diff --git a/src/ppopt/solver_interface/solver_interface.py b/src/ppopt/solver_interface/solver_interface.py index 6b04f8a..04ff0a3 100644 --- a/src/ppopt/solver_interface/solver_interface.py +++ b/src/ppopt/solver_interface/solver_interface.py @@ -3,12 +3,8 @@ import numpy from ..solver_interface.cvxopt_interface import solve_lp_cvxopt -from ..solver_interface.gurobi_solver_interface import ( - solve_lp_gurobi, - solve_milp_gurobi, - solve_miqp_gurobi, - solve_qp_gurobi, -) +from ..solver_interface.gurobi_solver_interface import (solve_qp_gurobi, + solve_lp_gurobi, solve_milp_gurobi, solve_miqp_gurobi) from ..solver_interface.quad_prog_interface import solve_qp_quadprog from .solver_interface_utils import SolverOutput @@ -26,9 +22,9 @@ def solver_not_supported(solver_name: str) -> None: # noinspection PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList def solve_miqp(Q: Matrix, c: Matrix, A: Matrix, b: Matrix, - equality_constraints: Iterable[int] = None, - bin_vars: Iterable[int] = None, verbose: bool = False, - get_duals: bool = True, deterministic_solver='gurobi') -> Optional[SolverOutput]: + equality_constraints: Iterable[int] = None, bin_vars: Iterable[int] = None, + verbose: bool = False, get_duals: bool = True, get_status: bool=False, + deterministic_solver='gurobi') -> Optional[SolverOutput]: r""" This is the breakout for solving mixed integer quadratic programs @@ -51,10 +47,14 @@ def solve_miqp(Q: Matrix, c: Matrix, A: Matrix, b: Matrix, :param equality_constraints: List of Equality constraints :param bin_vars: List of binary variable indices :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False, only + implemented for gurobi :param deterministic_solver: The underlying Solver to use, e.g. gurobi, ect - :return: A SolverOutput object if optima found, otherwise None. + :return: A SolverOutput object, or none if get_status is false and problem is + infeasible or unbounded """ if deterministic_solver == "gurobi": return solve_miqp_gurobi(Q, c, A, b, equality_constraints, bin_vars, verbose, get_duals) @@ -62,9 +62,10 @@ def solve_miqp(Q: Matrix, c: Matrix, A: Matrix, b: Matrix, solver_not_supported(deterministic_solver) -def solve_qp(Q: Matrix, c: Matrix, A: Matrix, b: Matrix, equality_constraints: Iterable[int] = None, - verbose=False, - get_duals=True, deterministic_solver='gurobi') -> Optional[SolverOutput]: +def solve_qp(Q: Matrix, c: Matrix, A: Matrix, b: Matrix, + equality_constraints: Iterable[int] = None, + verbose=False, get_duals=True, get_status: bool = False, + deterministic_solver='gurobi') -> Optional[SolverOutput]: r""" This is the breakout for solving quadratic programs @@ -85,11 +86,16 @@ def solve_qp(Q: Matrix, c: Matrix, A: Matrix, b: Matrix, equality_constraints: I :param b: Constraint RHS matrix, can be None :param equality_constraints: List of Equality constraints :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False, only + implemented for gurobi :param deterministic_solver: The underlying Solver to use, e.g. gurobi, ect - :return: A SolverOutput object if optima found, otherwise None. + :return: A SolverOutput object, or none if get_status is false and problem is + infeasible or unbounded """ + if deterministic_solver == "gurobi": return solve_qp_gurobi(Q, c, A, b, equality_constraints, verbose, get_duals) elif deterministic_solver == "quadprog": @@ -99,8 +105,9 @@ def solve_qp(Q: Matrix, c: Matrix, A: Matrix, b: Matrix, equality_constraints: I # noinspection PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList,PyArgumentList -def solve_lp(c: Matrix, A: Matrix, b: Matrix, equality_constraints=None, verbose=False, - get_duals=True, deterministic_solver='gurobi') -> Optional[SolverOutput]: +def solve_lp(c: Matrix, A: Matrix, b: Matrix, equality_constraints=None, + verbose=False, get_duals=True, get_status: bool = False, + deterministic_solver='gurobi') -> Optional[SolverOutput]: r""" This is the breakout for solving linear programs @@ -120,22 +127,31 @@ def solve_lp(c: Matrix, A: Matrix, b: Matrix, equality_constraints=None, verbose :param b: Constraint RHS matrix, can be None :param equality_constraints: List of Equality constraints :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False, only + implemented for gurobi :param deterministic_solver: The underlying Solver to use, e.g. gurobi, ect - :return: A SolverOutput object if optima found, otherwise None. + :return: A SolverOutput object, or none if get_status is false and problem is + infeasible or unbounded """ + if deterministic_solver == "gurobi": - return solve_lp_gurobi(c, A, b, equality_constraints, verbose, get_duals) + return solve_lp_gurobi(c, A, b, equality_constraints, + verbose, get_duals, get_status) + if deterministic_solver == 'glpk': return solve_lp_cvxopt(c, A, b, equality_constraints, verbose, get_duals) + else: solver_not_supported(deterministic_solver) -def solve_milp(c: Matrix, A: Matrix, b: Matrix, equality_constraints: Iterable[int] = None, - bin_vars: Iterable[int] = None, verbose=False, get_duals=True, - deterministic_solver='gurobi') -> Optional[SolverOutput]: +def solve_milp(c: Matrix, A: Matrix, b: Matrix, + equality_constraints: Iterable[int] = None, bin_vars: Iterable[int] = None, + verbose=False, get_duals=True, get_status: bool = False, + deterministic_solver='gurobi') -> Optional[SolverOutput]: r""" This is the breakout for solving mixed integer linear programs @@ -158,14 +174,19 @@ def solve_milp(c: Matrix, A: Matrix, b: Matrix, equality_constraints: Iterable[i :param equality_constraints: List of Equality constraints :param bin_vars: List of binary variable indices :param verbose: Flag for output of underlying Solver, default False - :param get_duals: Flag for returning dual variable of problem, default True (false for all mixed integer models) + :param get_duals: Flag for returning dual variable of problem, default True + (false for all mixed integer models) + :param get_status: Flag for returning gurobi solver status, default False, only + implemented for gurobi :param deterministic_solver: The underlying Solver to use, e.g. gurobi, ect - :return: A dictionary of the Solver outputs, or none if infeasible or unbounded. output['sol'] = primal - variables, output['dual'] = dual variables, output['obj'] = objective value, output['const'] = slacks, - output['active'] = active constraints. + :return: A SolverOutput object, or none if get_status is false and problem is + infeasible or unbounded """ + if deterministic_solver == "gurobi": - return solve_milp_gurobi(c, A, b, equality_constraints, bin_vars, verbose, get_duals) + return solve_milp_gurobi(c, A, b, equality_constraints, bin_vars, + verbose, get_duals, get_status) + else: solver_not_supported(deterministic_solver) diff --git a/src/ppopt/solver_interface/solver_interface_utils.py b/src/ppopt/solver_interface/solver_interface_utils.py index 6b2eb26..308e210 100644 --- a/src/ppopt/solver_interface/solver_interface_utils.py +++ b/src/ppopt/solver_interface/solver_interface_utils.py @@ -7,22 +7,25 @@ @dataclass class SolverOutput: """ - This is the generic Solver information object. This will be the general return object from all the back end - solvers. This was done to remove the need for the user to specialize IO for any particular Solver. It contains - all the information you would need for the optimization solution including, optimal value, optimal solution, - the active set, the value of the slack variables and the largange multipliers associated with every constraint ( - these are listed) as the dual variables. - - Members: - obj: objective value of the optimal solution \n - sol: x*, numpy array \n - - Optional Parameters -> None or numpy.ndarray type - - slack: the slacks associated with every constraint \n - equality_indices: the active set of the solution, including strongly and weakly active constraints \n + This is the generic Solver information object. This will be the general return + object from all the back end solvers. This was done to remove the need for the + user to specialize IO for any particular Solver. It contains all the information + you would need for the optimization solution including, optimal value, optimal + solution, the active set, the value of the slack variables and the largange + multipliers associated with every constraint (these are listed) as the dual + variables.\n + + Members:\n + obj: objective value of the optimal solution\n + sol: x*, numpy array\n + + Optional Parameters -> None or numpy.ndarray type\n + + slack: the slacks associated with every constraint\n + equality_indices: the active set of the solution, including strongly and weakly + active constraints\n dual: the lagrange multipliers associated with the problem\n - + status: the status code of the solver\n """ obj: float sol: numpy.ndarray @@ -31,13 +34,17 @@ class SolverOutput: active_set: Optional[numpy.ndarray] dual: Optional[numpy.ndarray] + status: Optional[int] + def __eq__(self, other): if not isinstance(other, SolverOutput): return NotImplemented - return numpy.allclose(self.slack, other.slack) and numpy.allclose(self.active_set, - other.active_set) and numpy.allclose( - self.dual, other.dual) and numpy.allclose(self.sol, other.sol) and numpy.allclose(self.obj, other.obj) + return (numpy.allclose(self.slack, other.slack) + and numpy.allclose(self.active_set, other.active_set) + and numpy.allclose(self.dual, other.dual) + and numpy.allclose(self.sol, other.sol) + and numpy.allclose(self.obj, other.obj)) def get_program_parameters(Q: Optional[numpy.ndarray], c: Optional[numpy.ndarray], A: Optional[numpy.ndarray],