Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miscellaneous updates #36

Merged
merged 3 commits into from
Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions galini/branch_and_bound/algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
45 changes: 18 additions & 27 deletions galini/branch_and_bound/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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_upper_bound)
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)
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)

Expand All @@ -219,30 +210,30 @@ 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:
if not node.has_solution:
return False
solution = node.state.lower_bound_solution
return solution and solution.status.is_success()

# 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:
Expand All @@ -251,5 +242,5 @@ def _nodes_minimum_lower_bound(self, nodes, upper_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
2 changes: 1 addition & 1 deletion galini/branch_and_cut/bound_reduction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions galini/branch_and_cut/branching.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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):
Expand Down
11 changes: 11 additions & 0 deletions galini/branch_and_cut/node_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions galini/galini.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions galini/relaxations/relax.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down