From d3b2c3e47f8f66c495f8d457fea132786bd033d5 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 11 Jun 2021 21:36:02 -0600 Subject: [PATCH 1/2] miscellaneous updates --- galini/branch_and_bound/algorithm.py | 9 ++++----- galini/branch_and_bound/tree.py | 22 +++++++++++----------- galini/branch_and_cut/bound_reduction.py | 2 +- galini/branch_and_cut/branching.py | 4 ++-- galini/branch_and_cut/node_storage.py | 11 +++++++++++ galini/galini.py | 2 ++ galini/relaxations/relax.py | 6 +++--- 7 files changed, 34 insertions(+), 22 deletions(-) diff --git a/galini/branch_and_bound/algorithm.py b/galini/branch_and_bound/algorithm.py index bf22f39..448e9af 100644 --- a/galini/branch_and_bound/algorithm.py +++ b/galini/branch_and_bound/algorithm.py @@ -22,7 +22,6 @@ from galini.branch_and_bound.solution import BabSolution, BabStatusInterrupted from galini.branch_and_bound.tree import BabTree from galini.branch_and_bound.telemetry import update_at_end_of_iteration -from galini.branch_and_cut.node_storage import RootNodeStorage from galini.config import ( NumericOption, IntegerOption, @@ -265,10 +264,10 @@ def _bab_loop(self, model, **kwargs): ) current_node_converged = is_close( solution.lower_bound, - solution.upper_bound, + tree.upper_bound, atol=self.bab_config['absolute_gap'], rtol=self.bab_config['relative_gap'], - ) + ) or solution.lower_bound > tree.upper_bound node_relaxation_is_feasible_or_unbounded = ( solution.lower_bound_solution is not None and @@ -285,8 +284,8 @@ def _bab_loop(self, model, **kwargs): # We won't explore this part of the tree anymore. # Add to fathomed nodes. self.logger.info( - 'Fathom node {}, converged? {}, upper_bound_solution {}', - current_node.coordinate, current_node_converged, solution.upper_bound_solution + 'Fathom node {}, lower_bound_solution: {}, tree upper bound: {}', + current_node.coordinate, solution.lower_bound, tree.upper_bound ) self.logger.log_prune_bab_node(current_node.coordinate) tree.fathom_node(current_node, update_nodes_visited=False) diff --git a/galini/branch_and_bound/tree.py b/galini/branch_and_bound/tree.py index 68e88e3..97e7f07 100644 --- a/galini/branch_and_bound/tree.py +++ b/galini/branch_and_bound/tree.py @@ -163,7 +163,7 @@ def _update_state(self, solution, is_root_node, update_nodes_visited=True): lower_bound_solution.status.is_success() ) if has_lower_bound_solution: - new_lower_bound_candidate = lower_bound_solution.objective_value() + new_lower_bound_candidate = lower_bound_solution.best_objective_estimate() else: new_lower_bound_candidate = None @@ -181,12 +181,12 @@ def _update_state(self, solution, is_root_node, update_nodes_visited=True): # If there are no open nodes, then the lower bound is the lowest # of the fathomed nodes lower bounds. if self.open_nodes: - new_lower_bound = self._open_nodes_lower_bound(new_upper_bound) + new_lower_bound = self._open_nodes_lower_bound(new_lower_bound_candidate) return self._set_new_state( new_lower_bound, new_upper_bound, update_nodes_visited) if self.fathomed_nodes: - new_lower_bound = self._fathomed_nodes_lower_bound(new_upper_bound) + new_lower_bound = self._fathomed_nodes_lower_bound(new_lower_bound_candidate) return self._set_new_state( new_lower_bound, new_upper_bound, update_nodes_visited) @@ -219,13 +219,13 @@ def _set_new_state(self, new_lower_bound, new_upper_bound, self.state = \ TreeState(new_lower_bound, new_upper_bound, new_nodes_visited) - def _open_nodes_lower_bound(self, upper_bound=None): + def _open_nodes_lower_bound(self, lower_bound=None): return self._nodes_minimum_lower_bound( self.open_nodes.values(), - upper_bound, + lower_bound, ) - def _fathomed_nodes_lower_bound(self, upper_bound=None): + def _fathomed_nodes_lower_bound(self, lower_bound=None): def _has_solution(node): if node.has_solution: return False @@ -235,14 +235,14 @@ def _has_solution(node): # Filter only nodes with a solution and remove infeasible nodes return self._nodes_minimum_lower_bound( [n for n in self.fathomed_nodes if _has_solution(n)], - upper_bound, + lower_bound, ) - def _nodes_minimum_lower_bound(self, nodes, upper_bound=None): - if upper_bound is None: - new_lower_bound = self.state.upper_bound + def _nodes_minimum_lower_bound(self, nodes, lower_bound=None): + if lower_bound is None: + new_lower_bound = self.state.lower_bound else: - new_lower_bound = upper_bound + new_lower_bound = lower_bound for node in nodes: if node.has_solution: diff --git a/galini/branch_and_cut/bound_reduction.py b/galini/branch_and_cut/bound_reduction.py index fd3c7df..8745baf 100644 --- a/galini/branch_and_cut/bound_reduction.py +++ b/galini/branch_and_cut/bound_reduction.py @@ -142,7 +142,7 @@ def perform_obbt_on_model(solver, model, linear_model, upper_bound, timelimit, r eps = mc.epsilon for var, new_lb, new_ub in zip(vars_to_tighten, *result): - original_var = model.find_component(var.getname(fully_qualified=True)) + original_var = model.find_component(var) if original_var is None: continue new_lb = best_lower_bound(var, new_lb, var.lb, eps) diff --git a/galini/branch_and_cut/branching.py b/galini/branch_and_cut/branching.py index 83412f1..2adf37c 100644 --- a/galini/branch_and_cut/branching.py +++ b/galini/branch_and_cut/branching.py @@ -80,7 +80,7 @@ def compute_branching_decision(model, linear_model, root_bounds, mip_solution, w if branching_variable is None: return None linear_branching_variable = \ - linear_model.find_component(branching_variable.getname(fully_qualified=True)) + linear_model.find_component(branching_variable) point = compute_branching_point(linear_branching_variable, mip_solution, lambda_, mc) return BranchingDecision(variable=branching_variable, point=point) @@ -135,7 +135,7 @@ def compute_branching_variable(problem, linear_problem, mip_solution, if branching_var is None: return None - return problem.find_component(branching_var.getname(fully_qualified=True)) + return problem.find_component(branching_var) def compute_nonlinear_infeasiblity_components(linear_problem, mip_solution): diff --git a/galini/branch_and_cut/node_storage.py b/galini/branch_and_cut/node_storage.py index b581025..33437dc 100644 --- a/galini/branch_and_cut/node_storage.py +++ b/galini/branch_and_cut/node_storage.py @@ -22,6 +22,7 @@ from galini.cuts.pool import CutNodeStorage, CutPool from galini.pyomo import safe_setlb, safe_setub from galini.relaxations.relax import RelaxationData +import math class BranchingDecision: @@ -90,6 +91,16 @@ def recompute_model_relaxation_bounds(self): aux_var = relaxation.get_aux_var() rhs_expr = relaxation.get_rhs_expr() new_lb, new_ub = compute_bounds_on_expr(rhs_expr) + if new_lb is None: + new_lb = -math.inf + if new_ub is None: + new_ub = math.inf + new_lb = max(new_lb, self._bounds.get(aux_var, (-math.inf, math.inf))[0]) + new_ub = min(new_ub, self._bounds.get(aux_var, (-math.inf, math.inf))[1]) + if new_lb == -math.inf: + new_lb = None + if new_ub == math.inf: + new_ub = None safe_setlb(aux_var, new_lb) safe_setub(aux_var, new_ub) diff --git a/galini/galini.py b/galini/galini.py index 33a5197..e14d37c 100644 --- a/galini/galini.py +++ b/galini/galini.py @@ -107,6 +107,8 @@ def solve(self, model, algorithm=None, clone_model=True, known_optimal_objective original_objective.deactivate() for var in model.component_data_objects(pe.Var, active=True): + if var.is_fixed(): + continue lb = var.lb if var.lb is not None else -np.inf ub = var.ub if var.ub is not None else np.inf value = var.value diff --git a/galini/relaxations/relax.py b/galini/relaxations/relax.py index 39bbc79..1c32be8 100644 --- a/galini/relaxations/relax.py +++ b/galini/relaxations/relax.py @@ -21,7 +21,7 @@ _relax_root_to_leaf_SumExpression, _relax_expr, ) -from coramin.relaxations import PWXSquaredRelaxation, PWUnivariateRelaxation +from coramin.relaxations import PWXSquaredRelaxation, PWUnivariateRelaxation, nonrelaxation_component_data_objects from coramin.utils.coramin_enums import RelaxationSide from pyomo.core.expr.numvalue import polynomial_degree from suspect.pyomo.quadratic import QuadraticExpression @@ -157,7 +157,7 @@ def relax(model, data, use_linear_relaxation=True): model.aux_vars = pe.VarList() model.aux_cons = pe.ConstraintList() - for obj in model.component_data_objects(ctype=pe.Objective, active=True): + for obj in nonrelaxation_component_data_objects(model, ctype=pe.Objective, active=True): degree = polynomial_degree(obj.expr) if degree is not None: if degree <= 1: @@ -171,7 +171,7 @@ def relax(model, data, use_linear_relaxation=True): new_body = relax_expression(model, obj.expr, relaxation_side, data) obj._expr = new_body - for cons in model.component_data_objects(ctype=pe.Constraint, active=True): + for cons in nonrelaxation_component_data_objects(model, ctype=pe.Constraint, active=True): relax_constraint(model, cons, data, inplace=True) update_relaxation_data(model, data) From 165beaa3d6fd58b617d8089ce2bd7cc2a7324853 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 14 Jun 2021 15:19:40 -0600 Subject: [PATCH 2/2] updates to update_state in bab tree --- galini/branch_and_bound/tree.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/galini/branch_and_bound/tree.py b/galini/branch_and_bound/tree.py index 97e7f07..1c8cc17 100644 --- a/galini/branch_and_bound/tree.py +++ b/galini/branch_and_bound/tree.py @@ -180,26 +180,17 @@ def _update_state(self, solution, is_root_node, update_nodes_visited=True): # of their lower bounds. # If there are no open nodes, then the lower bound is the lowest # of the fathomed nodes lower bounds. - if self.open_nodes: - new_lower_bound = self._open_nodes_lower_bound(new_lower_bound_candidate) - return self._set_new_state( - new_lower_bound, new_upper_bound, update_nodes_visited) - - if self.fathomed_nodes: - new_lower_bound = self._fathomed_nodes_lower_bound(new_lower_bound_candidate) - return self._set_new_state( - new_lower_bound, new_upper_bound, update_nodes_visited) + if self.open_nodes or self.fathomed_nodes: + new_lower_bound = min(self._open_nodes_lower_bound(new_lower_bound_candidate), + self._fathomed_nodes_lower_bound(new_lower_bound_candidate)) + return self._set_new_state(new_lower_bound, new_upper_bound, update_nodes_visited) return self._set_new_state(None, new_upper_bound, update_nodes_visited) def _update_lower_bound(self, update_nodes_visited=True): - if self.open_nodes: - new_lower_bound = self._open_nodes_lower_bound() - return self._set_new_state( - new_lower_bound, None, update_nodes_visited) - - if self.fathomed_nodes: - new_lower_bound = self._fathomed_nodes_lower_bound() + if self.open_nodes or self.fathomed_nodes: + new_lower_bound = min(self._open_nodes_lower_bound(), + self._fathomed_nodes_lower_bound()) return self._set_new_state( new_lower_bound, None, update_nodes_visited) @@ -227,7 +218,7 @@ def _open_nodes_lower_bound(self, lower_bound=None): def _fathomed_nodes_lower_bound(self, lower_bound=None): def _has_solution(node): - if node.has_solution: + if not node.has_solution: return False solution = node.state.lower_bound_solution return solution and solution.status.is_success() @@ -251,5 +242,5 @@ def _nodes_minimum_lower_bound(self, nodes, lower_bound=None): lower_bound = node.parent.lower_bound if lower_bound < new_lower_bound: - new_lower_bound = node.parent.lower_bound + new_lower_bound = lower_bound return new_lower_bound