def __init__(self, *args, **kwargs): kwargs.setdefault('ctype', Complementarity) kwargs.setdefault('dense', False) _init = tuple(_arg for _arg in (kwargs.pop('initialize', None), kwargs.pop('rule', None), kwargs.pop('expr', None)) if _arg is not None) if len(_init) > 1: raise ValueError( "Duplicate initialization: Complementarity() only accepts " "one of 'initialize=', 'rule=', and 'expr='") elif _init: _init = _init[0] else: _init = None self._init_rule = Initializer(_init, treat_sequences_as_mappings=False, allow_generators=True) if self._init_rule is not None: kwargs['rule'] = Complementarity._complementarity_rule Block.__init__(self, *args, **kwargs) # HACK to make the "counted call" syntax work. We wait until # after the base class is set up so that is_indexed() is # reliable. if self._init_rule is not None \ and self._init_rule.__class__ is IndexedCallInitializer: self._init_rule = CountedCallInitializer(self, self._init_rule)
def create_submodel_hp_block(instance): """ Creates highpoint relaxation with the given specified model; does not include any submodel or block that is deactivated """ block = Block(concrete=True) # get the objective for the master problem for c in instance.component_objects(Objective, descend_into=False): block.add_component(c.name, Reference(c)) # get the variables of the model (if there are more submodels, then # extraneous variables may be added to the block) for c in instance.component_objects(Var, sort=True, descend_into=True, active=True): block.add_component(c.name, Reference(c)) # get the constraints from the main model for c in instance.component_objects(Constraint, sort=True, descend_into=True, active=True): block.add_component(c.name, Reference(c)) # deactivate the highpoint relaxation block.deactivate() return block
def _generate_model(self): self.model = ConcreteModel() model = self.model model._name = self.description model.s = Set(initialize=[1,2]) model.x = Var() model.y = Var() model.z = Var(bounds=(0,None)) model.obj = Objective(model.s, rule=inactive_index_LP_obj_rule) model.OBJ = Objective(expr=model.x+model.y) model.obj[1].deactivate() model.OBJ.deactivate() model.c1 = ConstraintList() model.c1.add(model.x<=1) # index=1 model.c1.add(model.x>=-1) # index=2 model.c1.add(model.y<=1) # index=3 model.c1.add(model.y>=-1) # index=4 model.c1[1].deactivate() model.c1[4].deactivate() model.c2 = Constraint(model.s, rule=inactive_index_LP_c2_rule) model.b = Block() model.b.c = Constraint(expr=model.z >= 2) model.B = Block(model.s) model.B[1].c = Constraint(expr=model.z >= 3) model.B[2].c = Constraint(expr=model.z >= 1) model.b.deactivate() model.B.deactivate() model.B[2].activate()
def test_nested_blocks(self): """Test with nested blocks.""" m = ConcreteModel() m.b = Block() m.inactive_b = Block() m.inactive_b.deactivate() m.b.x = Var() m.b.x2 = Var(domain=Binary) m.b.x3 = Var(domain=Integers) m.inactive_b.x = Var() m.b.c = Constraint(expr=m.b.x == m.b.x2) m.inactive_b.c = Constraint(expr=m.b.x == 1) m.inactive_b.c2 = Constraint(expr=m.inactive_b.x == 15) model_size = build_model_size_report(m) self.assertEqual(model_size.activated.variables, 2) self.assertEqual(model_size.overall.variables, 4) self.assertEqual(model_size.activated.binary_variables, 1) self.assertEqual(model_size.overall.binary_variables, 1) self.assertEqual(model_size.activated.integer_variables, 0) self.assertEqual(model_size.overall.integer_variables, 1) self.assertEqual(model_size.activated.constraints, 1) self.assertEqual(model_size.overall.constraints, 3) self.assertEqual(model_size.activated.disjuncts, 0) self.assertEqual(model_size.overall.disjuncts, 0) self.assertEqual(model_size.activated.disjunctions, 0) self.assertEqual(model_size.overall.disjunctions, 0)
def makeNetworkDisjunction(minimize=True): """ creates a GDP model with pyomo.network components """ m = ConcreteModel() m.feed = feed = Block() m.wkbx = wkbx = Block() m.dest = dest = Block() m.orange = orange = Disjunct() m.blue = blue = Disjunct() m.orange_or_blue = Disjunction(expr=[orange,blue]) blue.blue_box = blue_box = Block() feed.x = Var(bounds=(0,1)) wkbx.x = Var(bounds=(0,1)) dest.x = Var(bounds=(0,1)) wkbx.inlet = ntwk.Port(initialize={"x":wkbx.x}) wkbx.outlet = ntwk.Port(initialize={"x":wkbx.x}) feed.outlet = ntwk.Port(initialize={"x":feed.x}) dest.inlet = ntwk.Port(initialize={"x":dest.x}) blue_box.x = Var(bounds=(0,1)) blue_box.x_wkbx = Var(bounds=(0,1)) blue_box.x_dest = Var(bounds=(0,1)) blue_box.inlet_feed = ntwk.Port(initialize={"x":blue_box.x}) blue_box.outlet_wkbx = ntwk.Port(initialize={"x":blue_box.x}) blue_box.inlet_wkbx = ntwk.Port(initialize={"x":blue_box.x_wkbx}) blue_box.outlet_dest = ntwk.Port(initialize={"x":blue_box.x_dest}) blue_box.multiplier_constr = Constraint(expr=blue_box.x_dest == \ 2*blue_box.x_wkbx) # orange arcs orange.a1 = ntwk.Arc(source=feed.outlet, destination=wkbx.inlet) orange.a2 = ntwk.Arc(source=wkbx.outlet, destination=dest.inlet) # blue arcs blue.a1 = ntwk.Arc(source=feed.outlet, destination=blue_box.inlet_feed) blue.a2 = ntwk.Arc(source=blue_box.outlet_wkbx, destination=wkbx.inlet) blue.a3 = ntwk.Arc(source=wkbx.outlet, destination=blue_box.inlet_wkbx) blue.a4 = ntwk.Arc(source=blue_box.outlet_dest, destination=dest.inlet) # maximize/minimize "production" if minimize: m.obj = Objective(expr=m.dest.x) else: m.obj = Objective(expr=m.dest.x, sense=maximize) # create a completely fixed model feed.x.fix(0.42) return m
def test_getname_error(self): m = ConcreteModel() m.b = Block() m.b.v = Var() m.c = Block() self.assertRaises(RuntimeError, m.b.v.getname, fully_qualified=True, relative_to=m.c)
def __init__(self, *args, **kwargs): if kwargs.pop('_deep_copying', None): # Hack for Python 2.4 compatibility # Deep copy will copy all items as necessary, so no need to # complete parsing return kwargs.setdefault('ctype', Disjunct) Block.__init__(self, *args, **kwargs)
def _create_transformation_block(self, context): new_xfrm_block_name = unique_component_name(context, 'logic_to_linear') new_xfrm_block = Block(doc="Transformation objects for logic_to_linear") setattr(context, new_xfrm_block_name, new_xfrm_block) new_xfrm_block.transformed_constraints = ConstraintList() new_xfrm_block.augmented_vars = BooleanVarList() new_xfrm_block.augmented_vars_asbinary = VarList( domain=Binary) return new_xfrm_block
def _f(m: Block): m.lmbda = Var(vertices, domain=NonNegativeReals) # 非负 m.y = Var(simplices, domain=Binary) # 二进制 m.a0 = Constraint(dimensions, rule=lambda m, d: sum(m.lmbda[v] * pointsT[d][v] for v in vertices) == input[d]) if bound == 'eq': m.a1 = Constraint(expr=output == sum(m.lmbda[v] * values[v] for v in vertices)) elif bound == 'lb': m.a1 = Constraint(expr=output <= sum(m.lmbda[v] * values[v] for v in vertices)) elif bound == 'ub': m.a1 = Constraint(expr=output >= sum(m.lmbda[v] * values[v] for v in vertices)) else: raise RuntimeError("bound值错误!bound=" + bound) m.b = Constraint(expr=sum(m.lmbda[v] for v in vertices) == 1) # generate a map from vertex index to simplex index, # which avoids an n^2 lookup when generating the # constraint vertex_to_simplex = [[] for _ in vertices] for s, simplex in enumerate(tri.simplices): for v in simplex: vertex_to_simplex[v].append(s) m.c0 = Constraint(vertices, rule=lambda m, v: m.lmbda[v] <= sum(m.y[s] for s in vertex_to_simplex[v])) m.c1 = Constraint(expr=sum(m.y[s] for s in simplices) == 1) return m
def _add_transformation_block(self, instance): # make a transformation block on instance to put transformed disjuncts # on transBlockName = unique_component_name(instance, '_pyomo_gdp_bigm_relaxation') transBlock = Block() instance.add_component(transBlockName, transBlock) transBlock.relaxedDisjuncts = Block(NonNegativeIntegers) transBlock.lbub = Set(initialize=['lb', 'ub']) return transBlock
def disj_rule(d, flag): m = d.model() if flag: d.b = Block() d.b.c = Constraint(expr=m.x == 0) d.add_component('b.c', Constraint(expr=m.y >= 9)) d.b.anotherblock = Block() d.b.anotherblock.c = Constraint(expr=m.y >= 11) d.bb = Block([1]) d.bb[1].c = Constraint(expr=m.x == 0) else: d.c = Constraint(expr=m.x >= 80)
def makeHierarchicalNested_DeclOrderMatchesInstantationOrder(): """Here, we put the disjunctive components on Blocks, but we do it in the same order that we declared the blocks, that is, on each block, decl order matches instantiation order.""" m = ConcreteModel() m.I = RangeSet(1, 4) m.x = Var(m.I, bounds=(-2, 6)) m.disjunct_block = Block() m.disjunction_block = Block() instantiate_hierarchical_nested_model(m) return m
def makeHierarchicalNested_DeclOrderOppositeInstantationOrder(): """Here, we declare the Blocks in the opposite order. This means that decl order will be *opposite* instantiation order, which means that we can break our targets preprocessing without even using targets if we are not correctly identifying what is nested in what!""" m = ConcreteModel() m.I = RangeSet(1, 4) m.x = Var(m.I, bounds=(-2, 6)) m.disjunction_block = Block() m.disjunct_block = Block() instantiate_hierarchical_nested_model(m) return m
def create_linear_dual_from_matrix_repn(c, b, P, d): blk = Block() n, m = P.shape # Add dual variables blk.var = Var(range(n), within=NonNegativeReals) # Dual objective blk.obj = Constraint(expr=quicksum(d[j] * b.var[j] for j in range(n)) <= b) # Dual constraints blk.cons = ConstraintList() for i in range(m): blk.cons.add(quicksum(P[i, j] * b.var[j] for j in range(n)) == c[i]) return blk
def dlog(m: Block, tri: qhull.Delaunay, values: List[float], input: List[SimpleVar] = None, output: SimpleVar = None, bound: str = 'eq', **kw): values = np.array(values).tolist() ndim = len(input) nsimplices = len(tri.simplices) npoints = len(tri.points) pointsT = list(zip(*tri.points)) # create index objects dimensions = list(range(ndim)) simplices = list(range(nsimplices)) # 跟单纯形 数量一致 vertices = list(range(npoints)) bound = bound.lower() L = int(math.ceil(math.log2(nsimplices))) L_Range = list(range(L)) vp = [0, 1, 2] # m.lmbda = Var(simplices, vp, domain=NonNegativeReals) # 非负 m.a0 = Constraint( dimensions, rule=lambda m, d: sum(m.lmbda[s, v] * pointsT[d][tri.simplices[s][v]] for s in simplices for v in vp) == input[d]) if bound == 'eq': m.a1 = Constraint(expr=output == sum( m.lmbda[s, v] * values[tri.simplices[s][v]] for s in simplices for v in vp)) elif bound == 'lb': m.a1 = Constraint( expr=output <= sum(m.lmbda[s, v] * values[tri.simplices[s][v]] for s in simplices for v in vp)) elif bound == 'ub': m.a1 = Constraint( expr=output >= sum(m.lmbda[s, v] * values[tri.simplices[s][v]] for s in simplices for v in vp)) else: raise RuntimeError("bound值错误!bound=" + bound) m.b1 = Constraint(expr=sum(m.lmbda[s, v] for s in simplices for v in vp) == 1) m.y = Var(L_Range, domain=Binary) # 二进制 m.c0 = Constraint(L_Range, rule=lambda m, l: sum(m.lmbda[s, v] for s in simplices if bin(s)[2:].zfill(L)[l] == '1' for v in vp) <= m.y[l]) m.c1 = Constraint(L_Range, rule=lambda m, l: sum(m.lmbda[s, v] for s in simplices if bin(s)[2:].zfill(L)[l] == '0' for v in vp) <= 1 - m.y[l]) return m
def _write_bundle_NL(worker, bundle, output_directory, linking_suffix_name, objective_suffix_name, symbolic_solver_labels): assert os.path.exists(output_directory) bundle_instance = worker._bundle_binding_instance_map[bundle.name] assert not hasattr(bundle_instance, ".tmpblock") tmpblock = Block(concrete=True) bundle_instance.add_component(".tmpblock", tmpblock) # # linking variable suffix # tmpblock.add_component(linking_suffix_name, Suffix(direction=Suffix.EXPORT)) linking_suffix = getattr(tmpblock, linking_suffix_name) # Loop over all nodes for the bundle except the leaf nodes, # which have no blended variables scenario_tree = worker.scenario_tree for stage in bundle.scenario_tree.stages[:-1]: for _node in stage.nodes: # get the node of off the real scenario tree # as this has the linked variable information node = scenario_tree.get_node(_node.name) master_variable = bundle_instance.find_component( "MASTER_BLEND_VAR_"+str(node.name)) for variable_id in node._standard_variable_ids: linking_suffix[master_variable[variable_id]] = variable_id # # objective weight suffix # tmpblock.add_component(objective_suffix_name, Suffix(direction=Suffix.EXPORT)) getattr(tmpblock, objective_suffix_name)[bundle_instance] = \ bundle._probability output_filename = os.path.join(output_directory, str(bundle.name)+".nl") bundle_instance.write( output_filename, io_options={'symbolic_solver_labels': symbolic_solver_labels}) bundle_instance.del_component(tmpblock)
def _generate_model(self): self.model = ConcreteModel() model = self.model model._name = self.description model.b = Block() model.B = Block([1,2,3]) model.a = Param(initialize=1.0, mutable=True) model.b.x = Var(within=NonNegativeReals) model.B[1].x = Var(within=NonNegativeReals) model.obj = Objective(expr=model.b.x + 3.0*model.B[1].x) model.obj.deactivate() model.B[2].c = Constraint(expr=-model.B[1].x <= -model.a) model.B[2].obj = Objective(expr=model.b.x + 3.0*model.B[1].x + 2) model.B[3].c = Constraint(expr=2.0 <= model.b.x/model.a - model.B[1].x <= 10)
def generate_norm_inf_objective_function(model, setpoint_model, discrete_only=False): """This function generates objective (PF-OA main problem) for minimum Norm Infinity distance to setpoint_model. Norm-Infinity distance of (x,y) = \max_i |x_i - y_i|. Args: model (Pyomo model): the model that needs new objective function. setpoint_model (Pyomo model): the model that provides the base point for us to calculate the distance. discrete_only (bool, optional): whether only optimize on distance between the discrete variables. Defaults to False. Returns: Objective: the norm infinity objective function """ # skip objective_value variable and slack_var variables var_filter = (lambda v: v.is_integer()) if discrete_only \ else (lambda v: v.name != 'MindtPy_utils.objective_value' and 'MindtPy_utils.feas_opt.slack_var' not in v.name) model_vars = list(filter(var_filter, model.MindtPy_utils.variable_list)) setpoint_vars = list( filter(var_filter, setpoint_model.MindtPy_utils.variable_list)) assert len(model_vars) == len( setpoint_vars), 'Trying to generate Norm Infinity objective function for models with different number of variables' model.MindtPy_utils.del_component('L_infinity_obj') obj_blk = model.MindtPy_utils.L_infinity_obj = Block() obj_blk.L_infinity_obj_var = Var(domain=Reals, bounds=(0, None)) obj_blk.abs_reform = ConstraintList() for v_model, v_setpoint in zip(model_vars, setpoint_vars): obj_blk.abs_reform.add( expr=v_model - v_setpoint.value >= -obj_blk.L_infinity_obj_var) obj_blk.abs_reform.add( expr=v_model - v_setpoint.value <= obj_blk.L_infinity_obj_var) return Objective(expr=obj_blk.L_infinity_obj_var)
def test_rules_with_None_in_set(self): def noarg_rule(b): b.args = () def onearg_rule(b, i): b.args = (i,) def twoarg_rule(b, i, j): b.args = (i,j) m = ConcreteModel() m.b1 = Block(rule=noarg_rule) self.assertEqual(m.b1.args, ()) m.b2 = Block([None], rule=onearg_rule) self.assertEqual(m.b2[None].args, (None,)) m.b3 = Block([(None,1)], rule=twoarg_rule) self.assertEqual(m.b3[None,1].args, ((None,1)))
def reformulate_integer_variables(model, config): integer_vars = list(v for v in model.component_data_objects( ctype=Var, descend_into=(Block, Disjunct)) if v.is_integer() and not v.fixed) if len(integer_vars) == 0: return # if no free integer variables, no reformulation needed. if config.reformulate_integer_vars_using is None: config.logger.warning( "Model contains unfixed integer variables. " "GDPopt will reformulate using base 2 binary variables " "by default. To specify a different method, see the " "reformulate_integer_vars_using configuration option.") config.reformulate_integer_vars_using = 'base2_binary' config.logger.info( "Reformulating integer variables using the %s strategy." % config.reformulate_integer_vars_using) # Set up reformulation block reform_block = model.GDPopt_utils.integer_reform = Block( doc="Holds variables and constraints for reformulating " "integer variables to binary variables.") reform_block.new_binary_var = Var( Any, domain=Binary, dense=False, doc="Binary variable with index (int_var.name, indx)") reform_block.integer_to_binary_constraint = Constraint( Any, doc="Equality constraints mapping the binary variable values " "to the integer variable value.") # check that variables are bounded and non-negative for int_var in integer_vars: if not (int_var.has_lb() and int_var.has_ub()): raise ValueError( "Integer variable %s is missing an " "upper or lower bound. LB: %s; UB: %s. " "GDPopt does not support unbounded integer variables." % (int_var.name, int_var.lb, int_var.ub)) if int_var.lb < 0: raise ValueError("Integer variable %s can be negative. " "GDPopt currently only supports positive integer " "variables." % (int_var.name)) # do the reformulation highest_power = floor(log(value(int_var.ub), 2)) var_name = int_var.name reform_block.integer_to_binary_constraint.add( var_name, expr=int_var == sum(reform_block.new_binary_var[var_name, pwr] * (2**pwr) for pwr in range(0, int(highest_power) + 1))) int_var.domain = NonNegativeReals config.logger.info("Reformulated %s integer variables using " "%s binary variables and %s constraints." % (len(integer_vars), len(reform_block.new_binary_var), len(reform_block.integer_to_binary_constraint)))
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 makeTwoTermDisj_IndexedConstraints(): """Single two-term disjunction with IndexedConstraints on both disjuncts. Does not bound the variables, so cannot be transformed by hull at all and requires specifying m values in bigm. """ m = ConcreteModel() m.s = Set(initialize=[1, 2]) m.a = Var(m.s) m.b = Block() def disj1_rule(disjunct): m = disjunct.model() def c_rule(d, s): return m.a[s] == 0 disjunct.c = Constraint(m.s, rule=c_rule) m.b.simpledisj1 = Disjunct(rule=disj1_rule) def disj2_rule(disjunct): m = disjunct.model() def c_rule(d, s): return m.a[s] <= 3 disjunct.c = Constraint(m.s, rule=c_rule) m.b.simpledisj2 = Disjunct(rule=disj2_rule) m.b.disjunction = Disjunction(expr=[m.b.simpledisj1, m.b.simpledisj2]) return m
def _process_container(blk, config): if not hasattr(blk, '_induced_linearity_info'): blk._induced_linearity_info = Block() else: assert blk._induced_linearity_info.ctype == Block eff_discr_vars = detect_effectively_discrete_vars( blk, config.equality_tolerance) # TODO will need to go through this for each disjunct, since it does # not (should not) descend into Disjuncts. # Determine the valid values for the effectively discrete variables possible_var_values = determine_valid_values(blk, eff_discr_vars, config) # Collect find bilinear expressions that can be reformulated using # knowledge of effectively discrete variables bilinear_map = _bilinear_expressions(blk) # Relevant constraints are those with bilinear terms that involve # effectively_discrete_vars processed_pairs = ComponentSet() for v1, var_values in possible_var_values.items(): v1_pairs = bilinear_map.get(v1, ()) for v2, bilinear_constrs in v1_pairs.items(): if (v1, v2) in processed_pairs: continue _process_bilinear_constraints(blk, v1, v2, var_values, bilinear_constrs) processed_pairs.add((v2, v1))
def makeTwoTermDisj_IndexedConstraints(): m = ConcreteModel() m.s = Set(initialize=[1, 2]) m.a = Var(m.s) m.b = Block() def disj1_rule(disjunct): m = disjunct.model() def c_rule(d, s): return m.a[s] == 0 disjunct.c = Constraint(m.s, rule=c_rule) m.b.simpledisj1 = Disjunct(rule=disj1_rule) def disj2_rule(disjunct): m = disjunct.model() def c_rule(d, s): return m.a[s] <= 3 disjunct.c = Constraint(m.s, rule=c_rule) m.b.simpledisj2 = Disjunct(rule=disj2_rule) m.b.disjunction = Disjunction(expr=[m.b.simpledisj1, m.b.simpledisj2]) return m
def _apply_to(self, instance, **kwds): # TODO: This data should be stored differently. We cannot nest this transformation with itself if getattr(instance, 'bilinear_data_', None) is None: instance.bilinear_data_ = Block() instance.bilinear_data_.cache = {} instance.bilinear_data_.vlist = VarList() instance.bilinear_data_.vlist_boolean = [] instance.bilinear_data_.IDX = Set() instance.bilinear_data_.disjuncts_ = Disjunct( instance.bilinear_data_.IDX * [0, 1]) instance.bilinear_data_.disjunction_data = {} instance.bilinear_data_.o_expr = {} instance.bilinear_data_.c_body = {} # # Iterate over all blocks # for block in instance.block_data_objects( active=True, sort=SortComponents.deterministic): self._transformBlock(block, instance) # # WEH: I wish I had a DisjunctList and DisjunctionList object... # def rule(block, i): return instance.bilinear_data_.disjunction_data[i] instance.bilinear_data_.disjunction_ = Disjunction( instance.bilinear_data_.IDX, rule=rule)
def cc(m: Block, tri: qhull.Delaunay, values: List[float], input: List[SimpleVar] = None, output: SimpleVar = None, bound: str = 'eq', **kw): values = np.array(values).tolist() ndim = len(input) nsimplices = len(tri.simplices) npoints = len(tri.points) pointsT = list(zip(*tri.points)) # create index objects dimensions = list(range(ndim)) simplices = list(range(nsimplices)) # 跟单纯形 数量一致 vertices = list(range(npoints)) bound = bound.lower() m.lmbda = Var(vertices, domain=NonNegativeReals) # 非负 m.y = Var(simplices, domain=Binary) # 二进制 # m.y = Var(simplices, domain=NonNegativeReals, bounds=(0, 1)) # 二进制 m.a0 = Constraint(dimensions, rule=lambda m, d: sum(m.lmbda[v] * pointsT[d][v] for v in vertices) == input[d]) if bound == 'eq': m.a1 = Constraint(expr=output == sum(m.lmbda[v] * values[v] for v in vertices)) elif bound == 'lb': m.a1 = Constraint(expr=output <= sum(m.lmbda[v] * values[v] for v in vertices)) elif bound == 'ub': m.a1 = Constraint(expr=output >= sum(m.lmbda[v] * values[v] for v in vertices)) else: raise RuntimeError("bound值错误!bound=" + bound) m.b = Constraint(expr=sum(m.lmbda[v] for v in vertices) == 1) # generate a map from vertex index to simplex index, # which avoids an n^2 lookup when generating the # constraint vertex_to_simplex = [[] for _ in vertices] for s, simplex in enumerate(tri.simplices): for v in simplex: vertex_to_simplex[v].append(s) m.c0 = Constraint( vertices, rule=lambda m, v: m.lmbda[v] <= sum(m.y[s] for s in vertex_to_simplex[v])) m.c1 = Constraint(expr=sum(m.y[s] for s in simplices) == 1) return m
def test_gdp_tree(self): m = ConcreteModel() m.x = Var() m.block = Block() m.block.d1 = Disjunct() m.block.d1.dd1 = Disjunct() m.disj1 = Disjunct() m.block.disjunction = Disjunction(expr=[m.block.d1, m.disj1]) m.block.d1.b = Block() m.block.d1.b.dd2 = Disjunct() m.block.d1.b.dd3 = Disjunct() m.block.d1.disjunction = Disjunction( expr=[m.block.d1.dd1, m.block.d1.b.dd2, m.block.d1.b.dd3]) m.block.d1.b.dd2.disjunction = Disjunction( expr=[[m.x >= 1], [m.x <= -1]]) targets = (m, ) knownBlocks = {} tree = get_gdp_tree(targets, m, knownBlocks) # check tree structure first vertices = tree.vertices self.assertEqual(len(vertices), 10) in_degrees = { m.block.d1: 1, m.block.disjunction: 0, m.disj1: 1, m.block.d1.disjunction: 1, m.block.d1.dd1: 1, m.block.d1.b.dd2: 1, m.block.d1.b.dd3: 1, m.block.d1.b.dd2.disjunction: 1, m.block.d1.b.dd2.disjunction.disjuncts[0]: 1, m.block.d1.b.dd2.disjunction.disjuncts[1]: 1 } for key, val in in_degrees.items(): self.assertEqual(tree.in_degree(key), val) # This should be deterministic, so we can just check the order topo_sort = [ m.block.disjunction, m.disj1, m.block.d1, m.block.d1.disjunction, m.block.d1.b.dd3, m.block.d1.b.dd2, m.block.d1.b.dd2.disjunction, m.block.d1.b.dd2.disjunction.disjuncts[1], m.block.d1.b.dd2.disjunction.disjuncts[0], m.block.d1.dd1 ] sort = tree.topological_sort() for i, node in enumerate(sort): self.assertIs(node, topo_sort[i])
def make_tiny_model_where_bounds_matter(self): m = ConcreteModel() m.b = Block() m.x = Var(bounds=(0, 15)) m.y = Var(bounds=(3, 5)) m.b.c = Constraint(expr=m.x + m.y <= 8) return m
def _transfer_var_references(self, fromBlock, toBlock): disjunctList = toBlock.relaxedDisjuncts for idx, disjunctBlock in iteritems(fromBlock.relaxedDisjuncts): # move all the of the local var references newblock = disjunctList[len(disjunctList)] newblock.localVarReferences = Block() newblock.localVarReferences.transfer_attributes_from( disjunctBlock.localVarReferences)
def test_active_parent_block(self): m = ConcreteModel() m.d1 = Block() m.d1.sub1 = Disjunct() m.d1.sub2 = Disjunct() m.d1.disj = Disjunction(expr=[m.d1.sub1, m.d1.sub2]) with self.assertRaises(GDP_Error): TransformationFactory('gdp.reclassify').apply_to(m)
def _add_relaxation_block(self, instance, name): # creates transformation block with a unique name based on name, adds it # to instance, and returns it. transBlockName = unique_component_name( instance, '_pyomo_gdp_cuttingplane_relaxation') transBlock = Block() instance.add_component(transBlockName, transBlock) return transBlockName, transBlock
def _write_scenario_NL(worker, scenario, output_directory, linking_suffix_name, objective_suffix_name, symbolic_solver_labels): assert os.path.exists(output_directory) instance = scenario._instance assert not hasattr(instance, ".tmpblock") tmpblock = Block(concrete=True) instance.add_component(".tmpblock", tmpblock) # # linking variable suffix # bySymbol = instance._ScenarioTreeSymbolMap.bySymbol tmpblock.add_component(linking_suffix_name, Suffix(direction=Suffix.EXPORT)) linking_suffix = getattr(tmpblock, linking_suffix_name) # Loop over all nodes for the scenario except the leaf node, # which has no blended variables for node in scenario._node_list[:-1]: for variable_id in node._standard_variable_ids: linking_suffix[bySymbol[variable_id]] = variable_id # # objective weight suffix # tmpblock.add_component(objective_suffix_name, Suffix(direction=Suffix.EXPORT)) getattr(tmpblock, objective_suffix_name)[instance] = \ scenario._probability output_filename = os.path.join(output_directory, str(scenario.name)+".nl") instance.write( output_filename, io_options={'symbolic_solver_labels': symbolic_solver_labels}) instance.del_component(tmpblock)
def prune_possible_values(block_scope, possible_values, config): # Prune the set of possible values by solving a series of feasibility # problems top_level_scope = block_scope.model() tmp_name = unique_component_name( top_level_scope, '_induced_linearity_prune_data') tmp_orig_blk = Block() setattr(top_level_scope, tmp_name, tmp_orig_blk) tmp_orig_blk._possible_values = possible_values tmp_orig_blk._possible_value_vars = list(v for v in possible_values) tmp_orig_blk._tmp_block_scope = (block_scope,) model = top_level_scope.clone() tmp_clone_blk = getattr(model, tmp_name) for obj in model.component_data_objects(Objective, active=True): obj.deactivate() for constr in model.component_data_objects( Constraint, active=True, descend_into=(Block, Disjunct)): if constr.body.polynomial_degree() not in (1, 0): constr.deactivate() if block_scope.type() == Disjunct: disj = tmp_clone_blk._tmp_block_scope[0] disj.indicator_var.fix(1) TransformationFactory('gdp.bigm').apply_to(model) tmp_clone_blk.test_feasible = Constraint() tmp_clone_blk._obj = Objective(expr=1) for eff_discr_var, vals in tmp_clone_blk._possible_values.items(): val_feasible = {} for val in vals: tmp_clone_blk.test_feasible.set_value(eff_discr_var == val) with SuppressConstantObjectiveWarning(): res = SolverFactory(config.pruning_solver).solve(model) if res.solver.termination_condition is tc.infeasible: val_feasible[val] = False tmp_clone_blk._possible_values[eff_discr_var] = set( v for v in tmp_clone_blk._possible_values[eff_discr_var] if val_feasible.get(v, True)) for i, var in enumerate(tmp_orig_blk._possible_value_vars): possible_values[var] = tmp_clone_blk._possible_values[ tmp_clone_blk._possible_value_vars[i]] return possible_values
def _get_block_model(self): model = ConcreteModel() model.s = Set(initialize=[1,2]) b = Block(concrete=True) b.s = Set(initialize=[1,2]) b.x = Var() b.X = Var(model.s) model.b1 = b.clone() model.b2 = b.clone() model.b3 = b.clone() model.b4 = b.clone() model.B1 = Block(model.s, rule=lambda _,i: b.clone()) model.B2 = Block(model.s, rule=lambda _,i: b.clone()) model.B3 = Block(model.s, rule=lambda _,i: b.clone()) model.B4 = Block(model.s, rule=lambda _,i: b.clone()) model.FirstStageCost = Expression(expr=0.0) model.SecondStageCost = Expression(expr=0.0) model.obj = Objective(expr=0.0) return model
def _process_bilinear_constraints(block, v1, v2, var_values, bilinear_constrs): # TODO check that the appropriate variable bounds exist. if not (v2.has_lb() and v2.has_ub()): logger.warning(textwrap.dedent("""\ Attempting to transform bilinear term {v1} * {v2} using effectively discrete variable {v1}, but {v2} is missing a lower or upper bound: ({v2lb}, {v2ub}). """.format(v1=v1, v2=v2, v2lb=v2.lb, v2ub=v2.ub)).strip()) return False blk = Block() unique_name = unique_component_name( block, ("%s_%s_bilinear" % (v1.local_name, v2.local_name)) .replace('[', '').replace(']', '')) block._induced_linearity_info.add_component(unique_name, blk) # TODO think about not using floats as indices in a set blk.valid_values = Set(initialize=var_values) blk.x_active = Var(blk.valid_values, domain=Binary, initialize=1) blk.v_increment = Var( blk.valid_values, domain=v2.domain, bounds=(v2.lb, v2.ub), initialize=v2.value) blk.v_defn = Constraint(expr=v2 == summation(blk.v_increment)) @blk.Constraint(blk.valid_values) def v_lb(blk, val): return v2.lb * blk.x_active[val] <= blk.v_increment[val] @blk.Constraint(blk.valid_values) def v_ub(blk, val): return blk.v_increment[val] <= v2.ub * blk.x_active[val] blk.select_one_value = Constraint(expr=summation(blk.x_active) == 1) # Categorize as case 1 or case 2 for bilinear_constr in bilinear_constrs: # repn = generate_standard_repn(bilinear_constr.body) # Case 1: no other variables besides bilinear term in constraint. v1 # (effectively discrete variable) is positive. # if (len(repn.quadratic_vars) == 1 and len(repn.linear_vars) == 0 # and repn.nonlinear_expr is None): # _reformulate_case_1(v1, v2, discrete_constr, bilinear_constr) # NOTE: Case 1 is left unimplemented for now, because it involves some # messier logic with respect to how the transformation needs to happen. # Case 2: this is everything else, but do we want to have a special # case if there are nonlinear expressions involved with the constraint? pass _reformulate_case_2(blk, v1, v2, bilinear_constr) pass
def BuildPiecewiseND(xvars, zvar, tri, zvals): """ Builds constraints defining a D-dimensional piecewise representation of the given triangulation. Args: xvars: A (D, 1) array of Pyomo variable objects representing the inputs of the piecewise function. zvar: A Pyomo variable object set equal to the output of the piecewise function. tri: A triangulation over the discretized variable domain. Required attributes: - points: An (npoints, D) shaped array listing the D-dimensional coordinates of the discretization points. - simplices: An (nsimplices, D+1) shaped array of integers specifying the D+1 indices of the points vector that define each simplex of the triangulation. zvals: An (npoints, 1) shaped array listing the value of the piecewise function at each of coordinates in the triangulation points array. Returns: A Pyomo Block object containing variables and constraints that define the piecewise function. """ b = Block(concrete=True) ndim = len(xvars) nsimplices = len(tri.simplices) npoints = len(tri.points) pointsT = list(zip(*tri.points)) # create index objects b.dimensions = RangeSet(0, ndim-1) b.simplices = RangeSet(0, nsimplices-1) b.vertices = RangeSet(0, npoints-1) # create variables b.lmda = Var(b.vertices, within=NonNegativeReals) b.y = Var(b.simplices, within=Binary) # create constraints def input_c_rule(b, d): pointsTd = pointsT[d] return xvars[d] == sum(pointsTd[v]*b.lmda[v] for v in b.vertices) b.input_c = Constraint(b.dimensions, rule=input_c_rule) b.output_c = Constraint(expr=\ zvar == sum(zvals[v]*b.lmda[v] for v in b.vertices)) b.convex_c = Constraint(expr=\ sum(b.lmda[v] for v in b.vertices) == 1) # generate a map from vertex index to simplex index, # which avoids an n^2 lookup when generating the # constraint vertex_to_simplex = [[] for v in b.vertices] for s, simplex in enumerate(tri.simplices): for v in simplex: vertex_to_simplex[v].append(s) def vertex_regions_rule(b, v): return b.lmda[v] <= \ sum(b.y[s] for s in vertex_to_simplex[v]) b.vertex_regions_c = \ Constraint(b.vertices, rule=vertex_regions_rule) b.single_region_c = Constraint(expr=\ sum(b.y[s] for s in b.simplices) == 1) return b
def _write_scenario_nl(worker, scenario, output_directory, io_options): assert os.path.exists(output_directory) instance = scenario._instance assert not hasattr(instance, ".schuripopt") tmpblock = Block(concrete=True) instance.add_component(".schuripopt", tmpblock) # # linking variable suffix # bySymbol = instance._ScenarioTreeSymbolMap.bySymbol tmpblock.add_component(_variable_id_suffix_name, Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)) linking_suffix = getattr(tmpblock, _variable_id_suffix_name) # Loop over all nodes for the scenario except the leaf node, # which has no blended variables for node in scenario._node_list[:-1]: node_name = node.name for variable_id in node._standard_variable_ids: # Assumes ASL uses 4-byte, signed integers to store suffixes, # and we need positive suffix values linking_suffix[bySymbol[variable_id]] = \ scenario_tree_id_to_pint32(node_name, variable_id) # make sure the conversion from scenario tree id to int # did not have any collisions _ids = list(linking_suffix.values()) assert len(_ids) == len(set(_ids)) # # objective weight suffix # tmpblock.add_component(_objective_weight_suffix_name, Suffix(direction=Suffix.EXPORT)) getattr(tmpblock, _objective_weight_suffix_name)[instance] = \ scenario.probability # take care to disable any advanced preprocessing flags since we # are not going through the scenario tree manager solver interface # TODO: resolve this preprocessing mess block_attrs = [] for block in instance.block_data_objects(active=True): attrs = [] for attr_name in ("_gen_obj_repn", "_gen_con_repn"): if hasattr(block, attr_name): attrs.append((attr_name, getattr(block, attr_name))) setattr(block, attr_name, True) if len(attrs): block_attrs.append((block, attrs)) output_filename = os.path.join(output_directory, str(scenario.name)+".nl") # write the model and obtain the symbol_map _, smap_id = instance.write( output_filename, format=ProblemFormat.nl, io_options=io_options) symbol_map = instance.solutions.symbol_map[smap_id] # reset preprocessing flags # TODO: resolve this preprocessing mess for block, attrs in block_attrs: for attr_name, attr_val in attrs: setattr(block, attr_name, attr_val) instance.del_component(tmpblock) return output_filename, symbol_map
def _write_bundle_nl(worker, bundle, output_directory, io_options): assert os.path.exists(output_directory) bundle_instance = worker._bundle_binding_instance_map[bundle.name] assert not hasattr(bundle_instance, ".schuripopt") tmpblock = Block(concrete=True) bundle_instance.add_component(".schuripopt", tmpblock) # # linking variable suffix # tmpblock.add_component(_variable_id_suffix_name, Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)) linking_suffix = getattr(tmpblock, _variable_id_suffix_name) # Loop over all nodes for the bundle except the leaf nodes, # which have no blended variables scenario_tree = worker.scenario_tree for stage in bundle.scenario_tree.stages[:-1]: for _node in stage.nodes: # get the node of off the real scenario tree # as this has the linked variable information node = scenario_tree.get_node(_node.name) node_name = node.name master_variable = bundle_instance.find_component( "MASTER_BLEND_VAR_"+str(node.name)) for variable_id in node._standard_variable_ids: # Assumes ASL uses 4-byte, signed integers to store suffixes, # and we need positive suffix values linking_suffix[master_variable[variable_id]] = \ scenario_tree_id_to_pint32(node_name, variable_id) # make sure the conversion from scenario tree id to int # did not have any collisions _ids = list(linking_suffix.values()) assert len(_ids) == len(set(_ids)) # # objective weight suffix # tmpblock.add_component(_objective_weight_suffix_name, Suffix(direction=Suffix.EXPORT)) getattr(tmpblock, _objective_weight_suffix_name)[bundle_instance] = \ bundle.probability # take care to disable any advanced preprocessing flags since we # are not going through the scenario tree manager solver interface # TODO: resolve this preprocessing mess block_attrs = [] for block in bundle_instance.block_data_objects(active=True): attrs = [] for attr_name in ("_gen_obj_repn", "_gen_con_repn"): if hasattr(block, attr_name): attrs.append((attr_name, getattr(block, attr_name))) setattr(block, attr_name, True) if len(attrs): block_attrs.append((block, attrs)) output_filename = os.path.join(output_directory, str(bundle.name)+".nl") # write the model and obtain the symbol_map _, smap_id = bundle_instance.write( output_filename, format=ProblemFormat.nl, io_options=io_options) symbol_map = bundle_instance.solutions.symbol_map[smap_id] # reset preprocessing flags # TODO: resolve this preprocessing mess for block, attrs in block_attrs: for attr_name, attr_val in attrs: setattr(block, attr_name, attr_val) bundle_instance.del_component(tmpblock) return output_filename, symbol_map
def _apply_to(self, model, **kwds): """Apply the transformation to the given model.""" config = self.CONFIG(kwds.pop('options', {})) config.set_value(kwds) integer_vars = list( v for v in model.component_data_objects( ctype=Var, descend_into=(Block, Disjunct)) if v.is_integer() and not v.fixed) if len(integer_vars) == 0: logger.info("Model has no free integer variables. No reformulation needed.") return vars_on_constr = ComponentSet() for c in model.component_data_objects( ctype=Constraint, descend_into=(Block, Disjunct), active=True): vars_on_constr.update(v for v in identify_variables(c.body, include_fixed=False) if v.is_integer()) if config.ignore_unused: num_vars_not_on_constr = len(integer_vars) - len(vars_on_constr) if num_vars_not_on_constr > 0: logger.info( "%s integer variables on the model are not attached to any constraints. " "Ignoring unused variables." ) integer_vars = list(vars_on_constr) logger.info( "Reformulating integer variables using the %s strategy." % config.strategy) # Set up reformulation block blk_name = unique_component_name(model, "_int_to_binary_reform") reform_block = Block( doc="Holds variables and constraints for reformulating " "integer variables to binary variables." ) setattr(model, blk_name, reform_block) reform_block.int_var_set = RangeSet(0, len(integer_vars) - 1) reform_block.new_binary_var = Var( Any, domain=Binary, dense=False, doc="Binary variable with index (int_var_idx, idx)") reform_block.integer_to_binary_constraint = Constraint( reform_block.int_var_set, doc="Equality constraints mapping the binary variable values " "to the integer variable value.") # check that variables are bounded and non-negative for idx, int_var in enumerate(integer_vars): if not (int_var.has_lb() and int_var.has_ub()): raise ValueError( "Integer variable %s is missing an " "upper or lower bound. LB: %s; UB: %s. " "Integer to binary reformulation does not support unbounded integer variables." % (int_var.name, int_var.lb, int_var.ub)) if int_var.lb < 0: raise ValueError( "Integer variable %s can be negative. " "Integer to binary reformulation currently only supports non-negative integer " "variables." % (int_var.name,) ) # do the reformulation highest_power = int(floor(log(value(int_var.ub), 2))) # TODO potentially fragile due to floating point reform_block.integer_to_binary_constraint.add( idx, expr=int_var == sum( reform_block.new_binary_var[idx, pwr] * (2 ** pwr) for pwr in range(0, highest_power + 1))) # Relax the original integer variable int_var.domain = NonNegativeReals logger.info( "Reformulated %s integer variables using " "%s binary variables and %s constraints." % (len(integer_vars), len(reform_block.new_binary_var), len(reform_block.integer_to_binary_constraint)))
def EXTERNAL_initialize_for_admm(manager, scenario): if manager.get_option("verbose"): print("Initializing scenario %s for admm algorithm" % (scenario.name)) admm_block = Block(concrete=True) assert not hasattr(scenario._instance, ".admm") scenario._instance.add_component(".admm", admm_block) # Augment the objective with lagrangian and penalty terms # and weight the original objective by the scenario probability. # The langrangian and penalty terms will be computed after # the parameters are created. user_cost_expression = scenario._instance_cost_expression admm_block.cost_expression = Expression(initialize=\ scenario.probability * user_cost_expression) admm_block.lagrangian_expression = Expression(initialize=0.0) admm_block.penalty_expression = Expression(initialize=0.0) # these are used in the objective, they can be toggled # between the expression above or something else (e.g., 0.0) admm_block.cost_term = Expression( initialize=admm_block.cost_expression) admm_block.lagrangian_term = Expression( initialize=admm_block.lagrangian_expression) admm_block.penalty_term = Expression( initialize=admm_block.penalty_expression) objective_direction = 1 if manager.objective_sense == maximize: objective_direction = -1 scenario._instance_objective.expr = \ admm_block.cost_term + \ admm_block.lagrangian_term * objective_direction + \ admm_block.penalty_term * objective_direction # add objective parameters to admm block for tree_node in scenario.node_list[:-1]: assert not tree_node.is_leaf_node() node_block = Block(concrete=True) admm_block.add_component(tree_node.name, node_block) node_block.node_index_set = Set( ordered=True, initialize=sorted(tree_node._standard_variable_ids)) node_block.z = Param(node_block.node_index_set, initialize=0.0, mutable=True) node_block.y = Param(node_block.node_index_set, initialize=0.0, mutable=True) node_block.rho = Param(node_block.node_index_set, initialize=0.0, mutable=True) for id_ in node_block.node_index_set: varname, index = tree_node._variable_ids[id_] var = scenario._instance.find_component(varname)[index] admm_block.lagrangian_expression.expr += \ node_block.y[id_] * (var - node_block.z[id_]) admm_block.penalty_expression.expr += \ (node_block.rho[id_] / 2.0) * (var - node_block.z[id_])**2 # The objective has changed so flag this if necessary. if manager.preprocessor is not None: manager.preprocessor.objective_updated[scenario.name] = True
def transformForTrustRegion(self,model,eflist): # transform and model into suitable form for TRF method # # Arguments: # model : pyomo model containing ExternalFunctions # eflist : a list of the external functions that will be # handled with TRF method rather than calls to compiled code efSet = set([id(x) for x in eflist]) TRF = Block() # Get all varibles seenVar = Set() allVariables = [] for var in model.component_data_objects(Var): if id(var) not in seenVar: seenVar.add(id(var)) allVariables.append(var) # This assumes that an external funtion call is present, required! model.add_component(unique_component_name(model,'tR'), TRF) TRF.y = VarList() TRF.x = VarList() TRF.conset = ConstraintList() TRF.external_fcns = [] TRF.exfn_xvars = [] # TODO: Copy constraints onto block so that transformation can be reversed. for con in model.component_data_objects(Constraint,active=True): con.set_value((con.lower, self.substituteEF(con.body,TRF,efSet), con.upper)) for obj in model.component_data_objects(Objective,active=True): obj.set_value(self.substituteEF(obj.expr,TRF,efSet)) ## Assume only one ative objective function here self.objective=obj if self.objective.sense == maximize: self.objective.expr = -1* self.objective.expr self.objective.sense = minimize # xvars and zvars are lists of x and z varibles as in the paper TRF.xvars = [] TRF.zvars = [] seenVar = Set() for varss in TRF.exfn_xvars: for var in varss: if id(var) not in seenVar: seenVar.add(id(var)) TRF.xvars.append(var) for var in allVariables: if id(var) not in seenVar: seenVar.add(id(var)) TRF.zvars.append(var) # TODO: build dict for exfn_xvars # assume it is not bottleneck of the code self.exfn_xvars_ind = [] for varss in TRF.exfn_xvars: listtmp = [] for var in varss: for i in range(len(TRF.xvars)): if(id(var)==id(TRF.xvars[i])): listtmp.append(i) break self.exfn_xvars_ind.append(listtmp) return TRF
def _apply_to(self, instance, **kwds): config = self.CONFIG(kwds.pop('options', {})) # We will let args override suffixes and estimate as a last # resort. More specific args/suffixes override ones anywhere in # the tree. Suffixes lower down in the tree override ones higher # up. if 'default_bigM' in kwds: logger.warn("DEPRECATED: the 'default_bigM=' argument has been " "replaced by 'bigM='") config.bigM = kwds.pop('default_bigM') config.set_value(kwds) bigM = config.bigM # make a transformation block to put transformed disjuncts on transBlockName = unique_component_name( instance, '_pyomo_gdp_bigm_relaxation') transBlock = Block() instance.add_component(transBlockName, transBlock) transBlock.relaxedDisjuncts = Block(Any) transBlock.lbub = Set(initialize=['lb', 'ub']) # this is a dictionary for keeping track of IndexedDisjuncts # and IndexedDisjunctions so that, at the end of the # transformation, we can check that the ones with no active # DisjstuffDatas are deactivated. transBlock.disjContainers = ComponentSet() targets = config.targets if targets is None: targets = (instance, ) _HACK_transform_whole_instance = True else: _HACK_transform_whole_instance = False for _t in targets: t = _t.find_component(instance) if t is None: raise GDP_Error( "Target %s is not a component on the instance!" % _t) if t.type() is Disjunction: if t.parent_component() is t: self._transformDisjunction(t, transBlock, bigM) else: self._transformDisjunctionData( t, transBlock, bigM, t.index()) elif t.type() in (Block, Disjunct): if t.parent_component() is t: self._transformBlock(t, transBlock, bigM) else: self._transformBlockData(t, transBlock, bigM) else: raise GDP_Error( "Target %s was not a Block, Disjunct, or Disjunction. " "It was of type %s and can't be transformed." % (t.name, type(t))) # Go through our dictionary of indexed things and deactivate # the containers that don't have any active guys inside of # them. So the invalid component logic will tell us if we # missed something getting transformed. for obj in transBlock.disjContainers: if not obj.active: continue for i in obj: if obj[i].active: break else: # HACK due to active flag implementation. # # Ideally we would not have to do any of this (an # ActiveIndexedComponent would get its active status by # querring the active status of all the contained Data # objects). As a fallback, we would like to call: # # obj._deactivate_without_fixing_indicator() # # However, the sreaightforward implementation of that # method would have unintended side effects (fixing the # contained _DisjunctData's indicator_vars!) due to our # class hierarchy. Instead, we will directly call the # relevant base class (safe-ish since we are verifying # that all the contained _DisjunctionData are # deactivated directly above). ActiveComponent.deactivate(obj) # HACK for backwards compatibility with the older GDP transformations # # Until the writers are updated to find variables on things # other than active blocks, we need to reclassify the Disjuncts # as Blocks after transformation so that the writer will pick up # all the variables that it needs (in this case, indicator_vars). if _HACK_transform_whole_instance: HACK_GDP_Disjunct_Reclassifier().apply_to(instance)
def apply_basic_step(disjunctions_or_constraints): # # Basic steps only apply to XOR'd disjunctions # disjunctions = list(obj for obj in disjunctions_or_constraints if obj.type() == Disjunction) constraints = list(obj for obj in disjunctions_or_constraints if obj.type() == Constraint) for d in disjunctions: if not d.xor: raise ValueError( "Basic steps can only be applied to XOR'd disjunctions\n\t" "(raised by disjunction %s)" % (d.name,)) if not d.active: logger.warning("Warning: applying basic step to a previously " "deactivated disjunction (%s)" % (d.name,)) ans = Block(concrete=True) ans.DISJUNCTIONS = Set(initialize=xrange(len(disjunctions))) ans.INDEX = Set( dimen=len(disjunctions), initialize=_squish_singletons(itertools.product( *tuple( xrange(len(d.disjuncts)) for d in disjunctions )))) # # Form the individual disjuncts for the new basic step # ans.disjuncts = Disjunct(ans.INDEX) for idx in ans.INDEX: # # Each source disjunct will be copied (cloned) into its own # subblock # ans.disjuncts[idx].src = Block(ans.DISJUNCTIONS) for i in ans.DISJUNCTIONS: tmp = _pseudo_clone(disjunctions[i].disjuncts[ idx[i] if isinstance(idx, tuple) else idx]) for k,v in list(iteritems( tmp.component_map() )): if k == 'indicator_var': continue tmp.del_component(k) ans.disjuncts[idx].src[i].add_component(k,v) # Copy in the constraints corresponding to the improper disjunctions ans.disjuncts[idx].improper_constraints = ConstraintList() for constr in constraints: for indx in constr: ans.disjuncts[idx].improper_constraints.add( (constr[indx].lower, constr[indx].body, constr[indx].upper) ) constr[indx].deactivate() # # Link the new disjunct indicator_var's to the original # indicator_var's. Since only one of the new # ans.indicator_links = ConstraintList() for i in ans.DISJUNCTIONS: for j in xrange(len(disjunctions[i].disjuncts)): ans.indicator_links.add( disjunctions[i].disjuncts[j].indicator_var == sum( ans.disjuncts[idx].indicator_var for idx in ans.INDEX if (idx[i] if isinstance(idx, tuple) else idx) == j )) # Form the new disjunction ans.disjunction = Disjunction(expr=[ans.disjuncts[i] for i in ans.INDEX]) # # Deactivate the old disjunctions / disjuncts # for i in ans.DISJUNCTIONS: disjunctions[i].deactivate() for d in disjunctions[i].disjuncts: d._deactivate_without_fixing_indicator() return ans