Example #1
0
    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)
Example #3
0
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