def calc_jacobians(solve_data, config): """ Generates a map of jacobians for the variables in the model This function generates a map of jacobians corresponding to the variables in the model and adds this ComponentMap to solve_data Parameters ---------- solve_data: MindtPy Data Container data container that holds solve-instance data config: MindtPy configurations contains the specific configurations for the algorithm """ # Map nonlinear_constraint --> Map( # variable --> jacobian of constraint wrt. variable) solve_data.jacobians = ComponentMap() if config.differentiate_mode == "reverse_symbolic": mode = differentiate.Modes.reverse_symbolic elif config.differentiate_mode == "sympy": mode = differentiate.Modes.sympy for c in solve_data.mip.MindtPy_utils.constraint_list: if c.body.polynomial_degree() in (1, 0): continue # skip linear constraints vars_in_constr = list(EXPR.identify_variables(c.body)) jac_list = differentiate(c.body, wrt_list=vars_in_constr, mode=mode) solve_data.jacobians[c] = ComponentMap( (var, jac_wrt_var) for var, jac_wrt_var in zip(vars_in_constr, jac_list))
def calc_jacobians(solve_data, config): """Generates a map of jacobians for the variables in the model. This function generates a map of jacobians corresponding to the variables in the model and adds this ComponentMap to solve_data. Parameters ---------- solve_data : MindtPySolveData Data container that holds solve-instance data. config : ConfigBlock The specific configurations for MindtPy. """ # Map nonlinear_constraint --> Map( # variable --> jacobian of constraint wrt. variable) solve_data.jacobians = ComponentMap() if config.differentiate_mode == 'reverse_symbolic': mode = differentiate.Modes.reverse_symbolic elif config.differentiate_mode == 'sympy': mode = differentiate.Modes.sympy for c in solve_data.mip.MindtPy_utils.nonlinear_constraint_list: vars_in_constr = list(EXPR.identify_variables(c.body)) jac_list = differentiate(c.body, wrt_list=vars_in_constr, mode=mode) solve_data.jacobians[c] = ComponentMap( (var, jac_wrt_var) for var, jac_wrt_var in zip(vars_in_constr, jac_list))
def calc_jacobians(solve_data, config): """Generate a map of jacobians.""" # Map nonlinear_constraint --> Map( # variable --> jacobian of constraint wrt. variable) solve_data.jacobians = ComponentMap() for c in solve_data.mip.MindtPy_utils.constraint_list: if c.body.polynomial_degree() in (1, 0): continue # skip linear constraints vars_in_constr = list(EXPR.identify_variables(c.body)) jac_list = differentiate(c.body, wrt_list=vars_in_constr, mode=differentiate.Modes.sympy) solve_data.jacobians[c] = ComponentMap( (var, jac_wrt_var) for var, jac_wrt_var in zip(vars_in_constr, jac_list))
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, include_fixed=False)) if len(constr_vars) >= 1000: mode = differentiate.Modes.reverse_numeric else: mode = differentiate.Modes.sympy jac_list = differentiate( constr.body, wrt_list=constr_vars, mode=mode) 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) try: new_oa_cut = ( copysign(1, sign_adjust * dual_value) * ( value(constr.body) - rhs + sum( value(jacobians[var]) * (var - value(var)) for var in jacobians)) - slack_var <= 0) if new_oa_cut.polynomial_degree() not in (1, 0): for var in jacobians: print(var.name, value(jacobians[var])) oa_cuts.add(expr=new_oa_cut) counter += 1 except ZeroDivisionError: config.logger.warning( "Zero division occured attempting to generate OA cut for constraint %s.\n" "Skipping OA cut generation for this constraint." % (constr.name,) ) # Simply continue on to the next constraint. config.logger.info('Added %s OA cuts' % counter)
def create_cuts_fme(transBlock_rHull, var_info, hull_to_bigm_map, rBigM_linear_constraints, rHull_vars, disaggregated_vars, norm, cut_threshold, zero_tolerance, integer_arithmetic): """Returns a cut which removes x* from the relaxed bigm feasible region. Finds all the constraints which are tight at xhat (assumed to be the solution currently in instance_rHull), and calculates a composite normal vector by summing the vectors normal to each of these constraints. Then Fourier-Motzkin elimination is used to project the disaggregated variables out of the polyhedron formed by the composite normal and the collection of tight constraints. This results in multiple cuts, of which we select one that cuts of x* by the greatest margin, as long as that margin is more than cut_threshold. If no cut satisfies the margin specified by cut_threshold, we return None. Parameters ----------- transBlock_rHull: transformation blcok on relaxed hull instance var_info: List of tuples (rBigM_var, rHull_var, xstar_param) hull_to_bigm_map: For expression substition, maps id(hull_var) to coresponding bigm var rBigM_linear_constraints: list of linear constraints in relaxed bigM rHull_vars: list of all variables in relaxed hull disaggregated_vars: ComponentSet of disaggregated variables in hull reformulation norm: norm used in the separation problem cut_threshold: Amount x* needs to be infeasible in generated cut in order to consider the cut for addition to the bigM model. zero_tolerance: Tolerance at which a float will be treated as 0 during Fourier-Motzkin elimination integer_arithmetic: boolean, whether or not to require Fourier-Motzkin Elimination does integer arithmetic. Only possible when all data is integer. """ instance_rHull = transBlock_rHull.model() # In the first iteration, we will compute a list of constraints that could # ever be interesting: Everything that involves at least one disaggregated # variable. if transBlock_rHull.component("constraints_for_FME") is None: _precompute_potentially_useful_constraints( transBlock_rHull, disaggregated_vars) tight_constraints = Block() conslist = tight_constraints.constraints = Constraint( NonNegativeIntegers) conslist.construct() something_interesting = False for constraint in transBlock_rHull.constraints_for_FME: multipliers = _constraint_tight(instance_rHull, constraint) for multiplier in multipliers: if multiplier: something_interesting = True f = constraint.body firstDerivs = differentiate(f, wrt_list=rHull_vars) normal_vec = [multiplier*value(_) for _ in firstDerivs] # check if constraint is linear if f.polynomial_degree() == 1: conslist[len(conslist)] = constraint.expr else: # we will use the linear approximation of this constraint at # x_hat conslist[len(conslist)] = _get_linear_approximation_expr( normal_vec, rHull_vars) # NOTE: we now have all the tight Constraints (in the pyomo sense of the # word "Constraint"), but we are missing some variable bounds. The ones for # the disaggregated variables will be added by FME # It is possible that the separation problem returned a point in the # interior of the convex hull. It is also possible that the only active # constraints do not involve the disaggregated variables. In these # situations, there are not constraints from which to create a valid cut. if not something_interesting: return None tight_constraints.construct() logger.info("Calling FME transformation on %s constraints to eliminate" " %s variables" % (len(tight_constraints.constraints), len(disaggregated_vars))) TransformationFactory('contrib.fourier_motzkin_elimination').\ apply_to(tight_constraints, vars_to_eliminate=disaggregated_vars, zero_tolerance=zero_tolerance, do_integer_arithmetic=integer_arithmetic, projected_constraints_name="fme_constraints") fme_results = tight_constraints.fme_constraints projected_constraints = [cons for i, cons in iteritems(fme_results)] # we created these constraints with the variables from rHull. We # actually need constraints for BigM and rBigM now! cuts = _get_constraint_exprs(projected_constraints, hull_to_bigm_map) # We likely have some cuts that duplicate other constraints now. We will # filter them to make sure that they do in fact cut off x*. If that's the # case, we know they are not already in the BigM relaxation. Because they # came from FME, they are very likely redundant, so we'll keep the best one # we find best = 0 best_cut = None cuts_to_keep = [] for i, cut in enumerate(cuts): # x* is still in rBigM, so we can just remove this constraint if it # is satisfied at x* logger.info("FME: Post-processing cut %s" % cut) if value(cut): logger.info("FME:\t Doesn't cut off x*") continue # we have found a constraint which cuts of x* by some convincing amount # and is not already in rBigM. cuts_to_keep.append(i) # We know cut is lb <= expr and that it's violated assert len(cut.args) == 2 cut_off = value(cut.args[0]) - value(cut.args[1]) if cut_off > cut_threshold and cut_off > best: best = cut_off best_cut = cut logger.info("FME:\t New best cut: Cuts off x* by %s." % best) # NOTE: this is not used right now, but it's not hard to imagine a world in # which we would want to keep multiple cuts from FME, so leaving it in for # now. cuts = [cuts[i] for i in cuts_to_keep] if best_cut is not None: return [best_cut] return None
def test_split_metis(self): m = self.m g = Graph() v1 = _VarNode(m.v1) v2 = _VarNode(m.v2) v3 = _VarNode(m.v3) v4 = _VarNode(m.v4) v5 = _VarNode(m.v5) v6 = _VarNode(m.v6) c1 = _ConNode(m.c1) c2 = _ConNode(m.c2) r1 = _RelNode(m.r1) r2 = _RelNode(m.r2) g.add_edge(v2, r2) g.add_edge(v3, r2) g.add_edge(v4, r2) g.add_edge(v1, c1) g.add_edge(v2, c1) g.add_edge(v3, c1) g.add_edge(v4, r1) g.add_edge(v5, r1) g.add_edge(v6, r1) g.add_edge(v4, c2) g.add_edge(v5, c2) g.add_edge(v6, c2) tree, partitioning_ratio = split_metis(graph=g) self.assertAlmostEqual(partitioning_ratio, 3 * 12 / (14 * 1 + 6 * 2 + 6 * 2)) children = list(tree.children) self.assertEqual(len(children), 2) graph_a = children[0] graph_b = children[1] if v1 in graph_b.nodes(): graph_a, graph_b = graph_b, graph_a graph_a_nodes = set(graph_a.nodes()) graph_b_nodes = set(graph_b.nodes()) self.assertIn(v1, graph_a_nodes) self.assertIn(v2, graph_a_nodes) self.assertIn(v3, graph_a_nodes) self.assertIn(v4, graph_b_nodes) self.assertIn(v5, graph_b_nodes) self.assertIn(v6, graph_b_nodes) self.assertIn(r2, graph_a_nodes) self.assertIn(c1, graph_a_nodes) self.assertIn(r1, graph_b_nodes) self.assertIn(c2, graph_b_nodes) self.assertEqual(len(graph_a_nodes), 6) self.assertEqual(len(graph_b_nodes), 5) v4_hat = list(graph_a_nodes - {v1, v2, v3, c1, r2})[0] graph_a_edges = set(graph_a.edges()) graph_b_edges = set(graph_b.edges()) self.assertTrue((v2, r2) in graph_a_edges or (r2, v2) in graph_a_edges) self.assertTrue((v3, r2) in graph_a_edges or (r2, v3) in graph_a_edges) self.assertTrue((v4_hat, r2) in graph_a_edges or (r2, v4_hat) in graph_a_edges) self.assertTrue((v1, c1) in graph_a_edges or (c1, v1) in graph_a_edges) self.assertTrue((v2, c1) in graph_a_edges or (c1, v2) in graph_a_edges) self.assertTrue((v3, c1) in graph_a_edges or (c1, v3) in graph_a_edges) self.assertTrue((v4, r1) in graph_b_edges or (r1, v4) in graph_b_edges) self.assertTrue((v5, r1) in graph_b_edges or (r1, v5) in graph_b_edges) self.assertTrue((v6, r1) in graph_b_edges or (r1, v6) in graph_b_edges) self.assertTrue((v4, c2) in graph_b_edges or (c2, v4) in graph_b_edges) self.assertTrue((v5, c2) in graph_b_edges or (c2, v5) in graph_b_edges) self.assertTrue((v6, c2) in graph_b_edges or (c2, v6) in graph_b_edges) self.assertEqual(len(graph_a_edges), 6) self.assertEqual(len(graph_b_edges), 6) edges_between_children = list(tree.edges_between_children) self.assertEqual(len(edges_between_children), 1) edge = edges_between_children[0] self.assertTrue((v4 is edge.node1 and v4_hat is edge.node2) or (v4 is edge.node2 and v4_hat is edge.node1)) new_model = TreeBlock(concrete=True) component_map = tree.build_pyomo_model(block=new_model) new_vars = list( coramin.relaxations.nonrelaxation_component_data_objects( new_model, ctype=pe.Var, descend_into=True, sort=True)) new_cons = list( coramin.relaxations.nonrelaxation_component_data_objects( new_model, ctype=pe.Constraint, active=True, descend_into=True, sort=True)) new_rels = list( coramin.relaxations.relaxation_data_objects(new_model, descend_into=True, active=True, sort=True)) self.assertEqual(len(new_vars), 7) self.assertEqual(len(new_cons), 3) self.assertEqual(len(new_rels), 2) self.assertEqual(len(new_model.children), 2) self.assertEqual(len(new_model.linking_constraints), 1) self.assertEqual(new_model.num_stages(), 2) stage0_vars = list( new_model.component_data_objects(pe.Var, descend_into=False, sort=True)) stage0_cons = list( new_model.component_data_objects(pe.Constraint, descend_into=False, sort=True, active=True)) stage0_rels = list( coramin.relaxations.relaxation_data_objects(new_model, descend_into=False, active=True, sort=True)) self.assertEqual(len(stage0_vars), 0) self.assertEqual(len(stage0_cons), 1) self.assertEqual(len(stage0_rels), 0) block_a = new_model.children[0] block_b = new_model.children[1] block_a_vars = ComponentSet( coramin.relaxations.nonrelaxation_component_data_objects( block_a, ctype=pe.Var, descend_into=True, sort=True)) block_b_vars = ComponentSet( coramin.relaxations.nonrelaxation_component_data_objects( block_b, ctype=pe.Var, descend_into=True, sort=True)) block_a_cons = ComponentSet( coramin.relaxations.nonrelaxation_component_data_objects( block_a, ctype=pe.Constraint, descend_into=True, active=True, sort=True)) block_b_cons = ComponentSet( coramin.relaxations.nonrelaxation_component_data_objects( block_b, ctype=pe.Constraint, descend_into=True, active=True, sort=True)) block_a_rels = ComponentSet( coramin.relaxations.relaxation_data_objects(block_a, descend_into=True, active=True, sort=True)) block_b_rels = ComponentSet( coramin.relaxations.relaxation_data_objects(block_b, descend_into=True, active=True, sort=True)) if component_map[m.v1] not in block_a_vars: block_a, block_b = block_b, block_a block_a_vars, block_b_vars = block_b_vars, block_a_vars block_a_cons, block_b_cons = block_b_cons, block_a_cons block_a_rels, block_b_rels = block_b_rels, block_a_rels self.assertEqual(len(block_a_vars), 4) self.assertEqual(len(block_a_cons), 1) self.assertEqual(len(block_a_rels), 1) self.assertEqual(len(block_b_vars), 3) self.assertEqual(len(block_b_cons), 1) self.assertEqual(len(block_b_rels), 1) v1 = component_map[m.v1] v2 = component_map[m.v2] v3 = component_map[m.v3] v4_a = block_a.vars['v4'] v4_b = block_b.vars['v4'] v5 = component_map[m.v5] v6 = component_map[m.v6] self.assertIs(v1, block_a.vars['v1']) self.assertIs(v2, block_a.vars['v2']) self.assertIs(v3, block_a.vars['v3']) self.assertIs(v5, block_b.vars['v5']) self.assertIs(v6, block_b.vars['v6']) self.assertEqual(v2.lb, -1) self.assertEqual(v2.ub, 1) self.assertEqual(v3.lb, -1) self.assertEqual(v3.ub, 1) self.assertEqual(v4_a.lb, -1) self.assertEqual(v4_a.ub, 1) self.assertEqual(v4_b.lb, -1) self.assertEqual(v4_b.ub, 1) self.assertEqual(v5.lb, -1) self.assertEqual(v5.ub, 1) self.assertEqual(v1.lb, None) self.assertEqual(v1.ub, None) self.assertEqual(v6.lb, None) self.assertEqual(v6.ub, None) linking_con = new_model.linking_constraints[1] linking_con_vars = ComponentSet(identify_variables(linking_con.body)) self.assertEqual(len(linking_con_vars), 2) self.assertIn(v4_a, linking_con_vars) self.assertIn(v4_b, linking_con_vars) derivs = differentiate(expr=linking_con.body, mode=differentiate.Modes.reverse_symbolic) self.assertTrue((derivs[v4_a] == 1 and derivs[v4_b] == -1) or (derivs[v4_a] == -1 and derivs[v4_b] == 1)) self.assertEqual(linking_con.lower, 0) self.assertEqual(linking_con.upper, 0) c1 = block_a.cons['c1'] c2 = block_b.cons['c2'] r1 = block_b.rels.r1 r2 = block_a.rels.r2 c1_vars = ComponentSet(identify_variables(c1.body)) c2_vars = ComponentSet(identify_variables(c2.body)) self.assertEqual(len(c1_vars), 3) self.assertEqual(len(c2_vars), 3) self.assertIn(v1, c1_vars) self.assertIn(v2, c1_vars) self.assertIn(v3, c1_vars) self.assertIn(v4_b, c2_vars) self.assertIn(v5, c2_vars) self.assertIn(v6, c2_vars) self.assertIs(r1.get_aux_var(), v6) self.assertIs(r2.get_aux_var(), v2) r1_rhs_vars = ComponentSet(r1.get_rhs_vars()) r2_rhs_vars = ComponentSet(r2.get_rhs_vars()) self.assertIn(v3, r2_rhs_vars) self.assertIn(v4_a, r2_rhs_vars) self.assertIn(v4_b, r1_rhs_vars) self.assertIn(v5, r1_rhs_vars) self.assertTrue( isinstance(r1, coramin.relaxations.PWMcCormickRelaxationData)) self.assertTrue( isinstance(r2, coramin.relaxations.PWMcCormickRelaxationData)) c1_derivs = differentiate(c1.body, mode=differentiate.Modes.reverse_symbolic) c2_derivs = differentiate(c2.body, mode=differentiate.Modes.reverse_symbolic) self.assertEqual(c1_derivs[v1], 1) self.assertEqual(c1_derivs[v2], -1) self.assertEqual(c1_derivs[v3], -1) self.assertEqual(c2_derivs[v4_b], -1) self.assertEqual(c2_derivs[v5], -1) self.assertEqual(c2_derivs[v6], 1) self.assertEqual(c1.lower, 0) self.assertEqual(c1.upper, 0) self.assertEqual(c2.lower, 0) self.assertEqual(c2.upper, 0)