def _apply_to(self, instance): for c in chain( self.get_adjustable_components(instance), self.get_adjustable_components(instance, component=Objective)): # Collect adjustable vars and uncparams adjvar = collect_adjustable(c) if id(adjvar) not in self._adjvars: self._adjvars[id(adjvar)] = adjvar # Create variables for LDR coefficients for i in adjvar: for u in adjvar[i].uncparams: parent = u.parent_component() if (adjvar.name, parent.name) not in self._coef_dict: coef = Var(adjvar.index_set(), parent.index_set()) coef_name = adjvar.name + '_' + parent.name + '_coef' setattr(instance, coef_name, coef) self._coef_dict[adjvar.name, parent.name] = coef # Create substitution map def coef(u): return self._coef_dict[adjvar.name, u.parent_component().name] def gen_index(u): if hasattr(u, 'index'): yield u.index() else: for i in u: yield i sub_map = { id(adjvar[i]): sum(u.parent_component()[j] * coef(u)[i, j] for u in adjvar[i].uncparams for j in gen_index(u)) for i in adjvar } self._expr_dict[adjvar.name] = sub_map # Replace AdjustableVar by LDR # Objectives if c.ctype is Objective: e_new = replace_expressions(c.expr, substitution_map=sub_map) c_new = Objective(expr=e_new, sense=c.sense) setattr(instance, c.name + '_ldr', c_new) # Constraints elif c.ctype is Constraint: e_new = replace_expressions(c.body, substitution_map=sub_map) if c.equality: repn = self.generate_repn_param(instance, e_new) c_new = ConstraintList() setattr(instance, c.name + '_ldr', c_new) # Check if repn.constant is an expression cons = repn.constant if cons.__class__ in nonpyomo_leaf_types: if cons != 0: raise ValueError("Can't reformulate constraint {} " "with numeric constant " "{}".format(c.name, cons)) elif cons.is_potentially_variable(): c_new.add(cons == 0) else: raise ValueError("Can't reformulate constraint {} with" " constant " "{}".format(c.name, cons)) # Add constraints for each uncparam for coef in repn.linear_coefs: c_new.add(coef == 0) for coef in repn.quadratic_coefs: c_new.add(coef == 0) else: def c_rule(x): return (c.lower, e_new, c.upper) c_new = Constraint(rule=c_rule) setattr(instance, c.name + '_ldr', c_new) c.deactivate() # Add constraints for bounds on AdjustableVar for name, sub_map in self._expr_dict.items(): adjvar = instance.find_component(name) cl = ConstraintList() setattr(instance, adjvar.name + '_bounds', cl) for i in adjvar: if adjvar[i].has_lb(): cl.add(adjvar[i].lb <= sub_map[id(adjvar[i])]) if adjvar[i].has_ub(): cl.add(adjvar[i].ub >= sub_map[id(adjvar[i])])
def _apply_to_impl(self, instance, config): vars_to_eliminate = config.vars_to_eliminate self.constraint_filter = config.constraint_filtering_callback self.do_integer_arithmetic = config.do_integer_arithmetic self.integer_tolerance = config.integer_tolerance self.zero_tolerance = config.zero_tolerance if vars_to_eliminate is None: raise RuntimeError( "The Fourier-Motzkin Elimination transformation " "requires the argument vars_to_eliminate, a " "list of Vars to be projected out of the model.") # make transformation block transBlockName = unique_component_name( instance, '_pyomo_contrib_fme_transformation') transBlock = Block() instance.add_component(transBlockName, transBlock) nm = config.projected_constraints_name if nm is None: projected_constraints = transBlock.projected_constraints = \ ConstraintList() else: # check that this component doesn't already exist if instance.component(nm) is not None: raise RuntimeError("projected_constraints_name was specified " "as '%s', but this is already a component " "on the instance! Please specify a unique " "name." % nm) projected_constraints = ConstraintList() instance.add_component(nm, projected_constraints) # collect all of the constraints # NOTE that we are ignoring deactivated constraints constraints = [] ctypes_not_to_transform = set( (Block, Param, Objective, Set, SetOf, Expression, Suffix, Var)) for obj in instance.component_data_objects( descend_into=Block, sort=SortComponents.deterministic, active=True): if obj.ctype in ctypes_not_to_transform: continue elif obj.ctype is Constraint: cons_list = self._process_constraint(obj) constraints.extend(cons_list) obj.deactivate( ) # the truth will be on our transformation block else: raise RuntimeError( "Found active component %s of type %s. The " "Fourier-Motzkin Elimination transformation can only " "handle purely algebraic models. That is, only " "Sets, Params, Vars, Constraints, Expressions, Blocks, " "and Objectives may be active on the model." % (obj.name, obj.ctype)) for obj in vars_to_eliminate: if obj.lb is not None: constraints.append({ 'body': generate_standard_repn(obj), 'lower': value(obj.lb), 'map': ComponentMap([(obj, 1)]) }) if obj.ub is not None: constraints.append({ 'body': generate_standard_repn(-obj), 'lower': -value(obj.ub), 'map': ComponentMap([(obj, -1)]) }) new_constraints = self._fourier_motzkin_elimination( constraints, vars_to_eliminate) # put the new constraints on the transformation block for cons in new_constraints: if self.constraint_filter is not None: try: keep = self.constraint_filter(cons) except: logger.error("Problem calling constraint filter callback " "on constraint with right-hand side %s and " "body:\n%s" % (cons['lower'], cons['body'].to_expression())) raise if not keep: continue lhs = cons['body'].to_expression(sort=True) lower = cons['lower'] assert type(lower) is int or type(lower) is float if type(lhs >= lower) is bool: if lhs >= lower: continue else: # This would actually make a lot of sense in this case... #projected_constraints.add(Constraint.Infeasible) raise RuntimeError("Fourier-Motzkin found the model is " "infeasible!") else: projected_constraints.add(lhs >= lower)
def create_ef_instance(scenario_tree, ef_instance_name="MASTER", verbose_output=False, generate_weighted_cvar=False, cvar_weight=None, risk_alpha=None, cc_indicator_var_name=None, cc_alpha=0.0): # # create the new and empty binding instance. # # scenario tree must be "linked" with a set of instances # to used this function scenario_instances = {} for scenario in scenario_tree.scenarios: if scenario._instance is None: raise ValueError("Cannot construct extensive form instance. " "The scenario tree does not appear to be linked " "to any Pyomo models. Missing model for scenario " "with name: %s" % (scenario.name)) scenario_instances[scenario.name] = scenario._instance binding_instance = ConcreteModel(name=ef_instance_name) root_node = scenario_tree.findRootNode() opt_sense = minimize \ if (scenario_tree._scenarios[0]._instance_objective.is_minimizing()) \ else maximize # # validate cvar options, if specified. # cvar_excess_vardatas = [] if generate_weighted_cvar: if (cvar_weight is None) or (cvar_weight < 0.0): raise RuntimeError( "Weight of CVaR term must be >= 0.0 - value supplied=" + str(cvar_weight)) if (risk_alpha is None) or (risk_alpha <= 0.0) or (risk_alpha >= 1.0): raise RuntimeError( "CVaR risk alpha must be between 0 and 1, exclusive - value supplied=" + str(risk_alpha)) if verbose_output: print("Writing CVaR weighted objective") print("CVaR term weight=" + str(cvar_weight)) print("CVaR alpha=" + str(risk_alpha)) print("") # create the eta and excess variable on a per-scenario basis, # in addition to the constraint relating to the two. cvar_eta_variable_name = "CVAR_ETA_" + str(root_node._name) cvar_eta_variable = Var() binding_instance.add_component(cvar_eta_variable_name, cvar_eta_variable) excess_var_domain = NonNegativeReals if (opt_sense == minimize) else \ NonPositiveReals compute_excess_constraint = \ binding_instance.COMPUTE_SCENARIO_EXCESS = \ ConstraintList() for scenario in scenario_tree._scenarios: cvar_excess_variable_name = "CVAR_EXCESS_" + scenario._name cvar_excess_variable = Var(domain=excess_var_domain) binding_instance.add_component(cvar_excess_variable_name, cvar_excess_variable) compute_excess_expression = cvar_excess_variable compute_excess_expression -= scenario._instance_cost_expression compute_excess_expression += cvar_eta_variable if opt_sense == maximize: compute_excess_expression *= -1 compute_excess_constraint.add( (0.0, compute_excess_expression, None)) cvar_excess_vardatas.append( (cvar_excess_variable, scenario._probability)) # the individual scenario instances are sub-blocks of the binding instance. for scenario in scenario_tree._scenarios: scenario_instance = scenario_instances[scenario._name] binding_instance.add_component(str(scenario._name), scenario_instance) # Now deactivate the scenario instance Objective since we are creating # a new master objective scenario._instance_objective.deactivate() # walk the scenario tree - create variables representing the # common values for all scenarios associated with that node, along # with equality constraints to enforce non-anticipativity. also # create expected cost variables for each node, to be computed via # constraints/objectives defined in a subsequent pass. master # variables are created for all nodes but those in the last # stage. expected cost variables are, for no particularly good # reason other than easy coding, created for nodes in all stages. if verbose_output: print("Creating variables for master binding instance") _cmap = binding_instance.MASTER_CONSTRAINT_MAP = ComponentMap() for stage in scenario_tree._stages[:-1]: # skip the leaf stage for tree_node in stage._tree_nodes: # create the master blending variable and constraints for this node master_blend_variable_name = \ "MASTER_BLEND_VAR_"+str(tree_node._name) master_blend_constraint_name = \ "MASTER_BLEND_CONSTRAINT_"+str(tree_node._name) # don't create master variables for derived # stage variables as they will not be used in # the problem, and their values would likely # never be consistent with what is stored on the # scenario variables master_variable_index = Set( initialize=sorted(tree_node._standard_variable_ids), ordered=True, name=master_blend_variable_name + "_index") binding_instance.add_component( master_blend_variable_name + "_index", master_variable_index) master_variable = Var(master_variable_index, name=master_blend_variable_name) binding_instance.add_component(master_blend_variable_name, master_variable) master_constraint = ConstraintList( name=master_blend_constraint_name) binding_instance.add_component(master_blend_constraint_name, master_constraint) tree_node_variable_datas = tree_node._variable_datas for variable_id in sorted(tree_node._standard_variable_ids): master_vardata = master_variable[variable_id] vardatas = tree_node_variable_datas[variable_id] # Don't blend fixed variables if not tree_node.is_variable_fixed(variable_id): for scenario_vardata, scenario_probability in vardatas: _cmap[scenario_vardata] = master_constraint.add( (master_vardata - scenario_vardata, 0.0)) if generate_weighted_cvar: cvar_cost_expression_name = "CVAR_COST_" + str(root_node._name) cvar_cost_expression = Expression(name=cvar_cost_expression_name) binding_instance.add_component(cvar_cost_expression_name, cvar_cost_expression) # create an expression to represent the expected cost at the root node binding_instance.EF_EXPECTED_COST = \ Expression(initialize=sum(scenario._probability * \ scenario._instance_cost_expression for scenario in scenario_tree._scenarios)) opt_expression = \ binding_instance.MASTER_OBJECTIVE_EXPRESSION = \ Expression(initialize=binding_instance.EF_EXPECTED_COST) if generate_weighted_cvar: cvar_cost_expression_name = "CVAR_COST_" + str(root_node._name) cvar_cost_expression = \ binding_instance.find_component(cvar_cost_expression_name) if cvar_weight == 0.0: # if the cvar weight is 0, then we're only # doing cvar - no mean. opt_expression.set_value(cvar_cost_expression) else: opt_expression.expr += cvar_weight * cvar_cost_expression binding_instance.MASTER = Objective(sense=opt_sense, expr=opt_expression) # CVaR requires the addition of a variable per scenario to # represent the cost excess, and a constraint to compute the cost # excess relative to eta. if generate_weighted_cvar: # add the constraint to compute the master CVaR variable value. iterate # over scenario instances to create the expected excess component first. cvar_cost_expression_name = "CVAR_COST_" + str(root_node._name) cvar_cost_expression = binding_instance.find_component( cvar_cost_expression_name) cvar_eta_variable_name = "CVAR_ETA_" + str(root_node._name) cvar_eta_variable = binding_instance.find_component( cvar_eta_variable_name) cost_expr = 1.0 for scenario_excess_vardata, scenario_probability in cvar_excess_vardatas: cost_expr += (scenario_probability * scenario_excess_vardata) cost_expr /= (1.0 - risk_alpha) cost_expr += cvar_eta_variable cvar_cost_expression.set_value(cost_expr) if cc_indicator_var_name is not None: if verbose_output is True: print("Creating chance constraint for indicator variable= " + cc_indicator_var_name) print("with alpha= " + str(cc_alpha)) if not isVariableNameIndexed(cc_indicator_var_name): cc_expression = 0 #?????? for scenario in scenario_tree._scenarios: scenario_instance = scenario_instances[scenario._name] scenario_probability = scenario._probability cc_var = scenario_instance.find_component( cc_indicator_var_name) cc_expression += scenario_probability * cc_var def makeCCRule(expression): def CCrule(model): return (1.0 - cc_alpha, cc_expression, None) return CCrule cc_constraint_name = "cc_" + cc_indicator_var_name cc_constraint = Constraint(name=cc_constraint_name, rule=makeCCRule(cc_expression)) binding_instance.add_component(cc_constraint_name, cc_constraint) else: print("multiple cc not yet supported.") variable_name, index_template = extractVariableNameAndIndex( cc_indicator_var_name) # verify that the root variable exists and grab it. # NOTE: we are using whatever scenario happens to laying around... it might be better to use the reference variable = scenario_instance.find_component(variable_name) if variable is None: raise RuntimeError("Unknown variable=" + variable_name + " referenced as the CC indicator variable.") # extract all "real", i.e., fully specified, indices matching the index template. match_indices = extractComponentIndices(variable, index_template) # there is a possibility that no indices match the input template. # if so, let the user know about it. if len(match_indices) == 0: raise RuntimeError("No indices match template=" + str(index_template) + " for variable=" + variable_name) # add the suffix to all variable values identified. for index in match_indices: variable_value = variable[index] cc_expression = 0 #?????? for scenario in scenario_tree._scenarios: scenario_instance = scenario_instances[scenario._name] scenario_probability = scenario._probability cc_var = scenario_instance.find_component( variable_name)[index] cc_expression += scenario_probability * cc_var def makeCCRule(expression): def CCrule(model): return (1.0 - cc_alpha, cc_expression, None) return CCrule indexasname = '' for c in str(index): if c not in ' ,': indexasname += c cc_constraint_name = "cc_" + variable_name + "_" + indexasname cc_constraint = Constraint(name=cc_constraint_name, rule=makeCCRule(cc_expression)) binding_instance.add_component(cc_constraint_name, cc_constraint) return binding_instance