def add_affine_cuts(nlp_result, solve_data, config): with time_code(solve_data.timing, "affine cut generation"): m = solve_data.linear_GDP if config.calc_disjunctive_bounds: with time_code(solve_data.timing, "disjunctive variable bounding"): TransformationFactory( 'contrib.compute_disj_var_bounds').apply_to( m, solver=config.mip_solver if config.obbt_disjunctive_bounds else None) config.logger.info("Adding affine cuts.") GDPopt = m.GDPopt_utils counter = 0 for var, val in zip(GDPopt.variable_list, nlp_result.var_values): if val is not None and not var.fixed: var.value = val for constr in constraints_in_True_disjuncts(m, config): # Note: this includes constraints that are deactivated in the current model (linear_GDP) disjunctive_var_bounds = disjunctive_bounds(constr.parent_block()) if constr.body.polynomial_degree() in (1, 0): continue vars_in_constr = list(identify_variables(constr.body)) if any(var.value is None for var in vars_in_constr): continue # a variable has no values # mcpp stuff mc_eqn = mc(constr.body, disjunctive_var_bounds) # mc_eqn = mc(constr.body) ccSlope = mc_eqn.subcc() cvSlope = mc_eqn.subcv() ccStart = mc_eqn.concave() cvStart = mc_eqn.convex() ub_int = min( constr.upper, mc_eqn.upper()) if constr.has_ub() else mc_eqn.upper() lb_int = max( constr.lower, mc_eqn.lower()) if constr.has_lb() else mc_eqn.lower() parent_block = constr.parent_block() # Create a block on which to put outer approximation cuts. aff_utils = parent_block.component('GDPopt_aff') if aff_utils is None: aff_utils = parent_block.GDPopt_aff = Block( doc="Block holding affine constraints") aff_utils.GDPopt_aff_cons = ConstraintList() aff_cuts = aff_utils.GDPopt_aff_cons concave_cut = sum(ccSlope[var] * (var - var.value) for var in vars_in_constr) + ccStart >= lb_int convex_cut = sum(cvSlope[var] * (var - var.value) for var in vars_in_constr) + cvStart <= ub_int aff_cuts.add(expr=concave_cut) aff_cuts.add(expr=convex_cut) counter += 2 config.logger.info("Added %s affine cuts" % counter)
def __init__(self, comp): self._vars = ComponentSet() self._saved_bounds = list() if comp.type() == Constraint: if comp.is_indexed(): for c in comp.values(): self._vars.update(identify_variables(c.body)) else: self._vars.update(identify_variables(comp.body)) else: for c in comp.component_data_objects(Constraint, descend_into=True, active=True, sort=True): self._vars.update(identify_variables(c.body))
def add_outer_approximation_cuts(nlp_result, solve_data, config): """Add outer approximation cuts to the linear GDP model.""" with time_code(solve_data.timing, 'OA cut generation'): m = solve_data.linear_GDP GDPopt = m.GDPopt_utils sign_adjust = -1 if solve_data.objective_sense == minimize else 1 # copy values over for var, val in zip(GDPopt.variable_list, nlp_result.var_values): if val is not None and not var.fixed: var.value = val # TODO some kind of special handling if the dual is phenomenally small? config.logger.debug('Adding OA cuts.') counter = 0 if not hasattr(GDPopt, 'jacobians'): GDPopt.jacobians = ComponentMap() for constr, dual_value in zip(GDPopt.constraint_list, nlp_result.dual_values): if dual_value is None or constr.body.polynomial_degree() in (1, 0): continue # Determine if the user pre-specified that OA cuts should not be # generated for the given constraint. parent_block = constr.parent_block() ignore_set = getattr(parent_block, 'GDPopt_ignore_OA', None) config.logger.debug('Ignore_set %s' % ignore_set) if (ignore_set and (constr in ignore_set or constr.parent_component() in ignore_set)): config.logger.debug( 'OA cut addition for %s skipped because it is in ' 'the ignore set.' % constr.name) continue config.logger.debug("Adding OA cut for %s with dual value %s" % (constr.name, dual_value)) # Cache jacobians jacobians = GDPopt.jacobians.get(constr, None) if jacobians is None: constr_vars = list(identify_variables(constr.body)) jac_list = differentiate(constr.body, wrt_list=constr_vars) jacobians = ComponentMap(zip(constr_vars, jac_list)) GDPopt.jacobians[constr] = jacobians # Create a block on which to put outer approximation cuts. oa_utils = parent_block.component('GDPopt_OA') if oa_utils is None: oa_utils = parent_block.GDPopt_OA = Block( doc="Block holding outer approximation cuts " "and associated data.") oa_utils.GDPopt_OA_cuts = ConstraintList() oa_utils.GDPopt_OA_slacks = VarList(bounds=(0, config.max_slack), domain=NonNegativeReals, initialize=0) oa_cuts = oa_utils.GDPopt_OA_cuts slack_var = oa_utils.GDPopt_OA_slacks.add() rhs = value(constr.lower) if constr.has_lb() else value( constr.upper) oa_cuts.add(expr=copysign(1, sign_adjust * dual_value) * (value(constr.body) - rhs + sum( value(jacobians[var]) * (var - value(var)) for var in jacobians)) - slack_var <= 0) counter += 1 config.logger.info('Added %s OA cuts' % counter)
def fbbt_block(m, tol=1e-4): """ Feasibility based bounds tightening (FBBT) for a block or model. This loops through all of the constraints in the block and performs FBBT on each constraint (see the docstring for fbbt_con()). Through this processes, any variables whose bounds improve by more than tol are collected, and FBBT is performed again on all constraints involving those variables. This process is continued until no variable bounds are improved by more than tol. Parameters ---------- m: pyomo.core.base.block.Block or pyomo.core.base.PyomoModel.ConcreteModel tol: float """ var_to_con_map = ComponentMap() var_lbs = ComponentMap() var_ubs = ComponentMap() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=True, sort=True): for v in identify_variables(c.body): if v not in var_to_con_map: var_to_con_map[v] = list() if v.lb is None: var_lbs[v] = -math.inf else: var_lbs[v] = value(v.lb) if v.ub is None: var_ubs[v] = math.inf else: var_ubs[v] = value(v.ub) var_to_con_map[v].append(c) improved_vars = ComponentSet() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=True, sort=True): fbbt_con(c) for v in identify_variables(c.body): if v.lb is not None: if value(v.lb) > var_lbs[v] + tol: improved_vars.add(v) var_lbs[v] = value(v.lb) if v.ub is not None: if value(v.ub) < var_ubs[v] - tol: improved_vars.add(v) var_ubs[v] = value(v.ub) while len(improved_vars) > 0: v = improved_vars.pop() for c in var_to_con_map[v]: fbbt_con(c) for _v in identify_variables(c.body): if _v.lb is not None: if value(_v.lb) > var_lbs[_v] + tol: improved_vars.add(_v) var_lbs[_v] = value(_v.lb) if _v.ub is not None: if value(_v.ub) < var_ubs[_v] - tol: improved_vars.add(_v) var_ubs[_v] = value(_v.ub)
def fbbt_block(m, tol=1e-4, deactivate_satisfied_constraints=False, update_variable_bounds=True, integer_tol=1e-4): """ Feasibility based bounds tightening (FBBT) for a block or model. This loops through all of the constraints in the block and performs FBBT on each constraint (see the docstring for fbbt_con()). Through this processes, any variables whose bounds improve by more than tol are collected, and FBBT is performed again on all constraints involving those variables. This process is continued until no variable bounds are improved by more than tol. Parameters ---------- m: pyomo.core.base.block.Block or pyomo.core.base.PyomoModel.ConcreteModel tol: float deactivate_satisfied_constraints: bool If deactivate_satisfied_constraints is True and a constraint is always satisfied, then the constranit will be deactivated update_variable_bounds: bool If update_variable_bounds is True, then the bounds on variables will be automatically updated. integer_tol: float If the lower bound computed on a binary variable is less than or equal to integer_tol, then the lower bound is left at 0. Otherwise, the lower bound is increased to 1. If the upper bound computed on a binary variable is greater than or equal to 1-integer_tol, then the upper bound is left at 1. Otherwise the upper bound is decreased to 0. Returns ------- new_var_bounds: ComponentMap A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed from FBBT. """ new_var_bounds = ComponentMap() var_to_con_map = ComponentMap() var_lbs = ComponentMap() var_ubs = ComponentMap() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=True, sort=True): for v in identify_variables(c.body): if v not in var_to_con_map: var_to_con_map[v] = list() if v.lb is None: var_lbs[v] = -math.inf else: var_lbs[v] = value(v.lb) if v.ub is None: var_ubs[v] = math.inf else: var_ubs[v] = value(v.ub) var_to_con_map[v].append(c) improved_vars = ComponentSet() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=True, sort=True): _new_var_bounds = fbbt_con( c, deactivate_satisfied_constraints=deactivate_satisfied_constraints, update_variable_bounds=update_variable_bounds, integer_tol=integer_tol) new_var_bounds.update(_new_var_bounds) for v, bnds in _new_var_bounds.items(): vlb, vub = bnds if vlb is not None: if vlb > var_lbs[v] + tol: improved_vars.add(v) var_lbs[v] = vlb if vub is not None: if vub < var_ubs[v] - tol: improved_vars.add(v) var_ubs[v] = vub while len(improved_vars) > 0: v = improved_vars.pop() for c in var_to_con_map[v]: _new_var_bounds = fbbt_con( c, deactivate_satisfied_constraints= deactivate_satisfied_constraints, update_variable_bounds=update_variable_bounds, integer_tol=integer_tol) new_var_bounds.update(_new_var_bounds) for _v, bnds in _new_var_bounds.items(): _vlb, _vub = bnds if _vlb is not None: if _vlb > var_lbs[_v] + tol: improved_vars.add(_v) var_lbs[_v] = _vlb if _vub is not None: if _vub < var_ubs[_v] - tol: improved_vars.add(_v) var_ubs[_v] = _vub return new_var_bounds