From a1c3133eb3020ec6674634fc1cf254efbcf637e4 Mon Sep 17 00:00:00 2001 From: Matthias Koenig Date: Fri, 7 Apr 2017 14:00:52 +0200 Subject: [PATCH 1/7] vector methods in interface implemented --- optlang/cplex_interface.py | 11 +++--- optlang/interface.py | 71 +++++++++++++++++++++++++++++++++++--- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/optlang/cplex_interface.py b/optlang/cplex_interface.py index 9c894ff6..a7e7cfe7 100644 --- a/optlang/cplex_interface.py +++ b/optlang/cplex_interface.py @@ -721,13 +721,12 @@ def _set_objective_direction(self, direction): {'min': self.problem.objective.sense.minimize, 'max': self.problem.objective.sense.maximize}[ direction]) - @property - def primal_values(self): + def _get_primal_values(self): try: - primal_values = collections.OrderedDict( - (variable.name, variable._round_primal_to_bounds(primal)) - for variable, primal in zip(self.variables, self.problem.solution.get_values()) - ) + # FIXME: the rounding should be performed on vector + primal_values = [variable._round_primal_to_bounds(primal) + for variable, primal in zip(self.variables, self.problem.solution.get_values())] + except CplexSolverError as err: raise SolverError(str(err)) return primal_values diff --git a/optlang/interface.py b/optlang/interface.py index 8005f211..c226f9a9 100644 --- a/optlang/interface.py +++ b/optlang/interface.py @@ -1202,6 +1202,15 @@ def status(self): """The solver status of the model.""" return self._status + def _get_variables_names(self): + """The names of model variables. + + Returns + ------- + list + """ + return [variable.name for variable in self.variables] + @property def primal_values(self): """The primal values of model variables. @@ -1210,8 +1219,19 @@ def primal_values(self): ------- collections.OrderedDict """ + return collections.OrderedDict( + zip(self._get_variables_names(), self._get_primal_values()) + ) + + def _get_primal_values(self): + """The primal values of model variables. + + Returns + ------- + list + """ # Fallback, if nothing faster is available - return collections.OrderedDict([(variable.name, variable.primal) for variable in self.variables]) + return [variable.primal for variable in self.variables] @property def reduced_costs(self): @@ -1221,8 +1241,28 @@ def reduced_costs(self): ------- collections.OrderedDict """ + return collections.OrderedDict( + zip(self._get_variables_names(), self._get_reduced_costs()) + ) + + def _get_reduced_costs(self): + """The reduced costs/dual values of all variables. + + Returns + ------- + list + """ # Fallback, if nothing faster is available - return collections.OrderedDict([(variable.name, variable.dual) for variable in self.variables]) + return [variable.dual for variable in self.variables] + + def _get_constraint_names(self): + """The names of model constraints. + + Returns + ------- + list + """ + return [constraint.name for constraint in self.constraints] @property def constraint_values(self): @@ -1232,19 +1272,42 @@ def constraint_values(self): ------- collections.OrderedDict """ + return collections.OrderedDict( + zip(self._get_constraint_names(), self._get_constraint_values()) + ) + + def _get_constraint_values(self): + """The primal values of all constraints. + + Returns + ------- + list + """ # Fallback, if nothing faster is available - return collections.OrderedDict([(constraint.name, constraint.primal) for constraint in self.constraints]) + return [constraint.primal for constraint in self.constraints] @property def shadow_prices(self): """The shadow prices of model (dual values of all constraints). + Returns + ------- + collections.OrderedDict + """ + return collections.OrderedDict( + zip(self._get_constraint_names(), self._get_shadow_prices()) + ) + + @property + def _get_shadow_prices(self): + """The shadow prices of model (dual values of all constraints). + Returns ------- collections.OrderedDict """ # Fallback, if nothing faster is available - return collections.OrderedDict([(constraint.name, constraint.dual) for constraint in self.constraints]) + return [constraint.dual for constraint in self.constraints] @property def is_integer(self): From 9f191ee6caa6b4c828c51395196a46e20e20f225 Mon Sep 17 00:00:00 2001 From: Matthias Koenig Date: Fri, 7 Apr 2017 14:03:39 +0200 Subject: [PATCH 2/7] cplex interface updated --- optlang/cplex_interface.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/optlang/cplex_interface.py b/optlang/cplex_interface.py index a7e7cfe7..130a0ef5 100644 --- a/optlang/cplex_interface.py +++ b/optlang/cplex_interface.py @@ -731,31 +731,25 @@ def _get_primal_values(self): raise SolverError(str(err)) return primal_values - @property - def reduced_costs(self): + def _get_reduced_costs(self): if self.is_integer: raise ValueError("Dual values are not well-defined for integer problems") try: - return collections.OrderedDict( - zip((variable.name for variable in self.variables), self.problem.solution.get_reduced_costs())) + return self.problem.solution.get_reduced_costs() except CplexSolverError as err: raise SolverError(str(err)) - @property - def constraint_values(self): + def _get_constraint_values(self): try: - return collections.OrderedDict( - zip((constraint.name for constraint in self.constraints), self.problem.solution.get_activity_levels())) + return self.problem.solution.get_activity_levels() except CplexSolverError as err: raise SolverError(str(err)) - @property - def shadow_prices(self): + def _get_shadow_prices(self): if self.is_integer: raise ValueError("Dual values are not well-defined for integer problems") try: - return collections.OrderedDict( - zip((constraint.name for constraint in self.constraints), self.problem.solution.get_dual_values())) + return self.problem.solution.get_dual_values() except CplexSolverError as err: raise SolverError(str(err)) From d1019e58749d15045236d2d303f04c611b7c33ad Mon Sep 17 00:00:00 2001 From: Matthias Koenig Date: Fri, 7 Apr 2017 14:13:16 +0200 Subject: [PATCH 3/7] glpk interface updated --- optlang/cplex_interface.py | 2 +- optlang/glpk_interface.py | 36 ++++++++++++++++-------------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/optlang/cplex_interface.py b/optlang/cplex_interface.py index 130a0ef5..d359cdd3 100644 --- a/optlang/cplex_interface.py +++ b/optlang/cplex_interface.py @@ -723,7 +723,7 @@ def _set_objective_direction(self, direction): def _get_primal_values(self): try: - # FIXME: the rounding should be performed on vector + # FIXME: implement vector based rounding function primal_values = [variable._round_primal_to_bounds(primal) for variable, primal in zip(self.variables, self.problem.solution.get_values())] diff --git a/optlang/glpk_interface.py b/optlang/glpk_interface.py index 3994f489..83a09e93 100644 --- a/optlang/glpk_interface.py +++ b/optlang/glpk_interface.py @@ -583,47 +583,43 @@ def objective(self, value): ) value.problem = self - @property - def primal_values(self): - primal_values = collections.OrderedDict() + def _get_primal_values(self): + primal_values = [] is_mip = self._glpk_is_mip() + # FIXME: vector function for lookup for index, variable in enumerate(self.variables): if is_mip: value = glp_mip_col_val(self.problem, index + 1) else: value = glp_get_col_prim(self.problem, index + 1) - primal_values[variable.name] = variable._round_primal_to_bounds(value) + # FIXME: vector function for rounding + primal_values.append(variable._round_primal_to_bounds(value)) return primal_values - @property - def reduced_costs(self): + def _get_reduced_costs(self): if self.is_integer: raise ValueError("Dual values are not well-defined for integer problems") - reduced_costs = collections.OrderedDict( - (var.name, glp_get_col_dual(self.problem, index + 1)) for index, var in enumerate(self.variables) - ) - return reduced_costs + # FIXME: vector function for lookup + return [glp_get_col_dual(self.problem, index + 1) for index, var in enumerate(self.variables)] - @property - def constraint_values(self): - dual_values = collections.OrderedDict() + def _get_constraint_values(self): + dual_values = [] is_mip = self._glpk_is_mip() + # FIXME: vector function for lookup for index, constraint in enumerate(self.constraints): if is_mip: value = glp_mip_row_val(self.problem, index + 1) else: value = glp_get_row_prim(self.problem, index + 1) - dual_values[constraint.name] = value + dual_values.append(value) return dual_values - @property - def shadow_prices(self): + def _get_shadow_prices(self): if self.is_integer: raise ValueError("Dual values are not well-defined for integer problems") - shadow_prices = collections.OrderedDict( - (constraint.name, glp_get_row_dual(self.problem, index + 1)) for index, constraint in enumerate(self.constraints) - ) - return shadow_prices + # FIXME: vector function for lookup + return [glp_get_row_dual(self.problem, index + 1) for index, constraint in enumerate(self.constraints)] + def to_lp(self): self.update() From 6dc6b718c9b732f294e5420908c54eb66ff5e459 Mon Sep 17 00:00:00 2001 From: Matthias Koenig Date: Fri, 7 Apr 2017 14:36:11 +0200 Subject: [PATCH 4/7] no vector functions exisiting glpk, updated interface --- optlang/glpk_interface.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/optlang/glpk_interface.py b/optlang/glpk_interface.py index 83a09e93..3e8aba61 100644 --- a/optlang/glpk_interface.py +++ b/optlang/glpk_interface.py @@ -586,27 +586,26 @@ def objective(self, value): def _get_primal_values(self): primal_values = [] is_mip = self._glpk_is_mip() - # FIXME: vector function for lookup + # no vector function in swiglpk (iteration required) for index, variable in enumerate(self.variables): if is_mip: value = glp_mip_col_val(self.problem, index + 1) else: value = glp_get_col_prim(self.problem, index + 1) - # FIXME: vector function for rounding primal_values.append(variable._round_primal_to_bounds(value)) return primal_values def _get_reduced_costs(self): if self.is_integer: raise ValueError("Dual values are not well-defined for integer problems") - # FIXME: vector function for lookup - return [glp_get_col_dual(self.problem, index + 1) for index, var in enumerate(self.variables)] + # no vector function in swiglpk (iteration required) + return [glp_get_col_dual(self.problem, index + 1) for index in range(len(self.variables))] def _get_constraint_values(self): dual_values = [] is_mip = self._glpk_is_mip() - # FIXME: vector function for lookup - for index, constraint in enumerate(self.constraints): + # no vector function in swiglpk (iteration required) + for index in range(len(self.constraints)): if is_mip: value = glp_mip_row_val(self.problem, index + 1) else: @@ -617,9 +616,8 @@ def _get_constraint_values(self): def _get_shadow_prices(self): if self.is_integer: raise ValueError("Dual values are not well-defined for integer problems") - # FIXME: vector function for lookup - return [glp_get_row_dual(self.problem, index + 1) for index, constraint in enumerate(self.constraints)] - + # no vector function in swiglpk (iteration required) + return [glp_get_row_dual(self.problem, index + 1) for index in range(len(self.constraints))] def to_lp(self): self.update() From 744fa8259ce029e3312acf886d8b7ba7a87df7da Mon Sep 17 00:00:00 2001 From: Matthias Koenig Date: Fri, 7 Apr 2017 15:57:14 +0200 Subject: [PATCH 5/7] glpk vector functions added --- optlang/glpk_interface.py | 43 ++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/optlang/glpk_interface.py b/optlang/glpk_interface.py index 3e8aba61..077f64b6 100644 --- a/optlang/glpk_interface.py +++ b/optlang/glpk_interface.py @@ -51,7 +51,8 @@ glp_set_mat_row, glp_set_col_bnds, glp_set_row_bnds, GLP_FR, GLP_UP, GLP_LO, GLP_FX, GLP_DB, glp_del_rows, \ glp_get_mat_row, glp_get_row_ub, glp_get_row_type, glp_get_row_lb, glp_get_row_name, glp_get_obj_coef, \ glp_get_obj_dir, glp_scale_prob, GLP_SF_AUTO, glp_get_num_int, glp_get_num_bin, glp_mip_col_val, \ - glp_mip_obj_val, glp_mip_status, GLP_ETMLIM, glp_adv_basis, glp_read_lp, glp_mip_row_val + glp_mip_obj_val, glp_mip_status, GLP_ETMLIM, glp_adv_basis, glp_read_lp, glp_mip_row_val, \ + get_col_primals, get_col_duals, get_row_primals, get_row_duals @@ -584,40 +585,40 @@ def objective(self, value): value.problem = self def _get_primal_values(self): - primal_values = [] - is_mip = self._glpk_is_mip() - # no vector function in swiglpk (iteration required) - for index, variable in enumerate(self.variables): - if is_mip: + if self._glpk_is_mip(): + # no vector function (element wise) + primal_values = [] + for index in range(len(self.variables)): value = glp_mip_col_val(self.problem, index + 1) - else: - value = glp_get_col_prim(self.problem, index + 1) - primal_values.append(variable._round_primal_to_bounds(value)) + primal_values.append(value) + else: + primal_values = get_col_primals(self.problem) + + # round primals + for i, variable in enumerate(self.variables): + primal_values[i] = variable._round_primal_to_bounds(primal_values[i]) return primal_values def _get_reduced_costs(self): if self.is_integer: raise ValueError("Dual values are not well-defined for integer problems") - # no vector function in swiglpk (iteration required) - return [glp_get_col_dual(self.problem, index + 1) for index in range(len(self.variables))] + return get_col_duals(self.problem) def _get_constraint_values(self): - dual_values = [] - is_mip = self._glpk_is_mip() - # no vector function in swiglpk (iteration required) - for index in range(len(self.constraints)): - if is_mip: + if self._glpk_is_mip(): + # no vector function (element wise) + dual_values = [] + for index in range(len(self.constraints)): value = glp_mip_row_val(self.problem, index + 1) - else: - value = glp_get_row_prim(self.problem, index + 1) - dual_values.append(value) + dual_values.append(value) + else: + dual_values = get_row_primals(self.problem) return dual_values def _get_shadow_prices(self): if self.is_integer: raise ValueError("Dual values are not well-defined for integer problems") - # no vector function in swiglpk (iteration required) - return [glp_get_row_dual(self.problem, index + 1) for index in range(len(self.constraints))] + return get_row_duals(self.problem) def to_lp(self): self.update() From 4810997096c4b675787e9929362822068058dc06 Mon Sep 17 00:00:00 2001 From: Matthias Koenig Date: Fri, 7 Apr 2017 17:38:30 +0200 Subject: [PATCH 6/7] primal rounding moved to interface --- optlang/cplex_interface.py | 5 +---- optlang/glpk_interface.py | 4 ---- optlang/interface.py | 6 +++++- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/optlang/cplex_interface.py b/optlang/cplex_interface.py index d359cdd3..e5aed90c 100644 --- a/optlang/cplex_interface.py +++ b/optlang/cplex_interface.py @@ -723,10 +723,7 @@ def _set_objective_direction(self, direction): def _get_primal_values(self): try: - # FIXME: implement vector based rounding function - primal_values = [variable._round_primal_to_bounds(primal) - for variable, primal in zip(self.variables, self.problem.solution.get_values())] - + primal_values = self.problem.solution.get_values() except CplexSolverError as err: raise SolverError(str(err)) return primal_values diff --git a/optlang/glpk_interface.py b/optlang/glpk_interface.py index 077f64b6..07be8e5b 100644 --- a/optlang/glpk_interface.py +++ b/optlang/glpk_interface.py @@ -593,10 +593,6 @@ def _get_primal_values(self): primal_values.append(value) else: primal_values = get_col_primals(self.problem) - - # round primals - for i, variable in enumerate(self.variables): - primal_values[i] = variable._round_primal_to_bounds(primal_values[i]) return primal_values def _get_reduced_costs(self): diff --git a/optlang/interface.py b/optlang/interface.py index c226f9a9..61502348 100644 --- a/optlang/interface.py +++ b/optlang/interface.py @@ -1215,12 +1215,16 @@ def _get_variables_names(self): def primal_values(self): """The primal values of model variables. + The primal values are rounded to the bounds. Returns ------- collections.OrderedDict """ + # round primals + primal_values = [variable._round_primal_to_bounds(primal) + for variable, primal in zip(self.variables, self._get_primal_values())] return collections.OrderedDict( - zip(self._get_variables_names(), self._get_primal_values()) + zip(self._get_variables_names(), primal_values) ) def _get_primal_values(self): From bc03c03639fefaf8e3ef8916c237bc9c44a173f7 Mon Sep 17 00:00:00 2001 From: Matthias Koenig Date: Fri, 7 Apr 2017 22:37:29 +0200 Subject: [PATCH 7/7] refactored primal rounding back to subinterfaces, property fixes --- optlang/cplex_interface.py | 9 +++++++++ optlang/glpk_interface.py | 26 ++++++++++++++------------ optlang/interface.py | 7 ++----- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/optlang/cplex_interface.py b/optlang/cplex_interface.py index e5aed90c..5ef27fd9 100644 --- a/optlang/cplex_interface.py +++ b/optlang/cplex_interface.py @@ -721,6 +721,15 @@ def _set_objective_direction(self, direction): {'min': self.problem.objective.sense.minimize, 'max': self.problem.objective.sense.maximize}[ direction]) + @property + def primal_values(self): + # round primals + primal_values = [variable._round_primal_to_bounds(primal) + for variable, primal in zip(self.variables, self._get_primal_values())] + return collections.OrderedDict( + zip(self._get_variables_names(), primal_values) + ) + def _get_primal_values(self): try: primal_values = self.problem.solution.get_values() diff --git a/optlang/glpk_interface.py b/optlang/glpk_interface.py index 07be8e5b..ef85c023 100644 --- a/optlang/glpk_interface.py +++ b/optlang/glpk_interface.py @@ -584,16 +584,22 @@ def objective(self, value): ) value.problem = self + @property + def primal_values(self): + # round primals + primal_values = [variable._round_primal_to_bounds(primal) + for variable, primal in zip(self.variables, self._get_primal_values())] + return collections.OrderedDict( + zip(self._get_variables_names(), primal_values) + ) + def _get_primal_values(self): if self._glpk_is_mip(): # no vector function (element wise) - primal_values = [] - for index in range(len(self.variables)): - value = glp_mip_col_val(self.problem, index + 1) - primal_values.append(value) + return [glp_mip_col_val(self.problem, index + 1) + for index in range(len(self.variables))] else: - primal_values = get_col_primals(self.problem) - return primal_values + return get_col_primals(self.problem) def _get_reduced_costs(self): if self.is_integer: @@ -603,13 +609,9 @@ def _get_reduced_costs(self): def _get_constraint_values(self): if self._glpk_is_mip(): # no vector function (element wise) - dual_values = [] - for index in range(len(self.constraints)): - value = glp_mip_row_val(self.problem, index + 1) - dual_values.append(value) + return [glp_mip_row_val(self.problem, index + 1) for index in range(len(self.constraints))] else: - dual_values = get_row_primals(self.problem) - return dual_values + return get_row_primals(self.problem) def _get_shadow_prices(self): if self.is_integer: diff --git a/optlang/interface.py b/optlang/interface.py index 61502348..92117cb3 100644 --- a/optlang/interface.py +++ b/optlang/interface.py @@ -1220,11 +1220,8 @@ def primal_values(self): ------- collections.OrderedDict """ - # round primals - primal_values = [variable._round_primal_to_bounds(primal) - for variable, primal in zip(self.variables, self._get_primal_values())] return collections.OrderedDict( - zip(self._get_variables_names(), primal_values) + zip(self._get_variables_names(), self._get_primal_values()) ) def _get_primal_values(self): @@ -1302,7 +1299,7 @@ def shadow_prices(self): zip(self._get_constraint_names(), self._get_shadow_prices()) ) - @property + def _get_shadow_prices(self): """The shadow prices of model (dual values of all constraints).