Beispiel #1
0
    def _generate_base_scenario_tree(self, model, variable_stage_assignments):

        stage_cost_annotation = locate_annotations(
            model,
            PySP_StageCostAnnotation,
            max_allowed=1)
        if len(stage_cost_annotation) == 0:
            raise ValueError("Reference model is missing stage cost "
                             "annotation: %s" % (PySP_StageCostAnnotation.__name__))
        else:
            assert len(stage_cost_annotation) == 1
            stage_cost_annotation = stage_cost_annotation[0][1]
        stage_cost_assignments = ComponentMap(
            stage_cost_annotation.expand_entries())

        stage1_cost = None
        stage2_cost = None
        for cdata, stagenum in stage_cost_assignments.items():
            if stagenum == 1:
                stage1_cost = cdata
            elif stagenum == 2:
                stage2_cost = cdata
        if stage1_cost is None:
            raise ValueError("Missing stage cost annotation for time stage: 1")
        if stage2_cost is None:
            raise ValueError("Missing stage cost annotation for time stage: 2")
        assert stage1_cost != stage2_cost

        #
        # Create a dummy 1-scenario scenario tree
        #

        stm = CreateAbstractScenarioTreeModel()
        stm.Stages.add('Stage1')
        stm.Stages.add('Stage2')
        stm.Nodes.add('RootNode')
        stm.Nodes.add('LeafNode')
        stm.Scenarios.add('ReferenceScenario')
        stm = stm.create_instance()
        stm.NodeStage['RootNode'] = 'Stage1'
        stm.ConditionalProbability['RootNode'] = 1.0
        stm.NodeStage['LeafNode'] = 'Stage2'
        stm.Children['RootNode'].add('LeafNode')
        stm.Children['LeafNode'].clear()
        stm.ConditionalProbability['LeafNode'] = 1.0
        stm.ScenarioLeafNode['ReferenceScenario'] = 'LeafNode'

        stm.StageCost['Stage1'] = stage1_cost.name
        stm.StageCost['Stage2'] = stage2_cost.name
        for var, (stagenum, derived) in variable_stage_assignments.items():
            stagelabel = 'Stage'+str(stagenum)
            if not derived:
                stm.StageVariables[stagelabel].add(var.name)
            else:
                stm.StageDerivedVariables[second_stage].add(var.name)

        scenario_tree = ScenarioTree(scenariotreeinstance=stm)
        scenario_tree.linkInInstances(
            {'ReferenceScenario': self.reference_model})
        return scenario_tree
Beispiel #2
0
    def _generate_base_scenario_tree(self, model, variable_stage_assignments):

        stage_cost_annotation = locate_annotations(model,
                                                   PySP_StageCostAnnotation,
                                                   max_allowed=1)
        if len(stage_cost_annotation) == 0:
            raise ValueError("Reference model is missing stage cost "
                             "annotation: %s" %
                             (PySP_StageCostAnnotation.__name__))
        else:
            assert len(stage_cost_annotation) == 1
            stage_cost_annotation = stage_cost_annotation[0][1]
        stage_cost_assignments = ComponentMap(
            stage_cost_annotation.expand_entries())

        stage1_cost = None
        stage2_cost = None
        for cdata, stagenum in stage_cost_assignments.items():
            if stagenum == 1:
                stage1_cost = cdata
            elif stagenum == 2:
                stage2_cost = cdata
        if stage1_cost is None:
            raise ValueError("Missing stage cost annotation for time stage: 1")
        if stage2_cost is None:
            raise ValueError("Missing stage cost annotation for time stage: 2")
        assert stage1_cost != stage2_cost

        #
        # Create a dummy 1-scenario scenario tree
        #

        stm = CreateAbstractScenarioTreeModel()
        stm.Stages.add('Stage1')
        stm.Stages.add('Stage2')
        stm.Nodes.add('RootNode')
        stm.Nodes.add('LeafNode')
        stm.Scenarios.add('ReferenceScenario')
        stm = stm.create_instance()
        stm.NodeStage['RootNode'] = 'Stage1'
        stm.ConditionalProbability['RootNode'] = 1.0
        stm.NodeStage['LeafNode'] = 'Stage2'
        stm.Children['RootNode'].add('LeafNode')
        stm.Children['LeafNode'].clear()
        stm.ConditionalProbability['LeafNode'] = 1.0
        stm.ScenarioLeafNode['ReferenceScenario'] = 'LeafNode'

        stm.StageCost['Stage1'] = stage1_cost.name
        stm.StageCost['Stage2'] = stage2_cost.name
        for var, (stagenum, derived) in variable_stage_assignments.items():
            stagelabel = 'Stage' + str(stagenum)
            if not derived:
                stm.StageVariables[stagelabel].add(var.name)
            else:
                stm.StageDerivedVariables[second_stage].add(var.name)

        scenario_tree = ScenarioTree(scenariotreeinstance=stm)
        scenario_tree.linkInInstances(
            {'ReferenceScenario': self.reference_model})
        return scenario_tree
Beispiel #3
0
    def _map_variable_stages(self, model):

        variable_stage_annotation = locate_annotations(
            model,
            PySP_VariableStageAnnotation,
        max_allowed=1)
        if len(variable_stage_annotation) == 0:
            raise ValueError(
                "Reference model is missing variable stage "
                "annotation: %s" % (PySP_VariableStageAnnotation.__name__))
        else:
            assert len(variable_stage_annotation) == 1
            variable_stage_annotation = variable_stage_annotation[0][1]
        variable_stage_assignments = ComponentMap(
            variable_stage_annotation.expand_entries(
                expand_containers=False))
        if len(variable_stage_assignments) == 0:
            raise ValueError("At least one variable stage assignment is required.")

        min_stagenumber = min(variable_stage_assignments.values(),
                              key=lambda x: x[0])[0]
        max_stagenumber = max(variable_stage_assignments.values(),
                              key=lambda x: x[0])[0]
        if max_stagenumber > 2:
            for vardata, (stagenum, derived) in variable_stage_assignments.items():
                if stagenum > 2:
                    raise ValueError(
                        "Implicit stochastic programs must be two-stage, "
                        "but variable with name '%s' has been annotated with "
                        "stage number: %s" % (vardata.name, stagenum))

        stage_to_variables_map = {}
        stage_to_variables_map[1] = []
        stage_to_variables_map[2] = []
        for vardata in model.component_data_objects(
                Var,
                active=True,
                descend_into=True,
                sort=SortComponents.alphabetizeComponentAndIndex):
            stagenumber, derived = variable_stage_assignments.get(vardata, (2, False))
            if (stagenumber != 1) and (stagenumber != 2):
                raise ValueError("Invalid stage annotation for variable with "
                                 "name '%s'. Stage assignment must be 1 or 2. "
                                 "Current value: %s"
                                 % (vardata.name, stagenumber))
            if (stagenumber == 1):
                stage_to_variables_map[1].append((vardata, derived))
            else:
                assert stagenumber == 2
                stage_to_variables_map[2].append((vardata, derived))

        variable_to_stage_map = ComponentMap()
        for stagenum, stagevars in stage_to_variables_map.items():
            for vardata, derived in stagevars:
                variable_to_stage_map[vardata] = (stagenum, derived)

        return (stage_to_variables_map,
                variable_to_stage_map,
                variable_stage_assignments)
Beispiel #4
0
 def __init__(self):
     self.data = ComponentMap()
     try:
         self.declare(None)
         assert None in self.data
         self.default = self.data[None]
         del self.data[None]
     except TypeError:
         self.default = None
Beispiel #5
0
    def pyro_sample_sp(self, size, **kwds):
        assert size > 0
        model = self.reference_model.clone()

        scenario_tree_model = \
            self._create_scenario_tree_model(size)
        factory = ScenarioTreeInstanceFactory(
            model=self.reference_model, scenario_tree=scenario_tree_model)
        options = \
            ScenarioTreeManagerClientPyro.register_options()
        for key in kwds:
            options[key] = kwds[key]
        manager = ScenarioTreeManagerClientPyro(options, factory=factory)
        try:
            init = manager.initialize(async_call=True)
            pcuids = ComponentMap()
            for param in self.stochastic_data:
                pcuids[param] = ComponentUID(param)
            init.complete()
            for scenario in manager.scenario_tree.scenarios:
                data = []
                for param, dist in self.stochastic_data.items():
                    data.append((pcuids[param], dist.sample()))
                manager.invoke_function(
                    "_update_data",
                    thisfile,
                    invocation_type=InvocationType.OnScenario(scenario.name),
                    function_args=(data, ),
                    oneway_call=True)
            manager.reference_model = model
        except:
            manager.close()
            raise
        return manager
Beispiel #6
0
class PySP_Annotation(object):
    def __init__(self):
        self.data = ComponentMap()
        try:
            self.declare(None)
            assert None in self.data
            self.default = self.data[None]
            del self.data[None]
        except TypeError:
            self.default = None

    def declare(self, component, *args, **kwds):
        raise NotImplementedError("This method is abstract")

    def pprint(self, *args, **kwds):
        self.data.pprint(*args, **kwds)
Beispiel #7
0
def _extract_stochastic_data(model):
    stochastic_data_annotation = locate_annotations(
        model,
        StochasticDataAnnotation,
        max_allowed=1)
    if len(stochastic_data_annotation) == 0:
        raise ValueError(
            "Reference model is missing stochastic data "
            "annotation: %s" % (StochasticDataAnnotation.__name__))
    else:
        assert len(stochastic_data_annotation) == 1
        stochastic_data_annotation = stochastic_data_annotation[0][1]
    stochastic_data = ComponentMap(
        stochastic_data_annotation.expand_entries())
    if len(stochastic_data) == 0:
        raise ValueError("At least one stochastic data "
                         "entry is required.")
    for paramdata in stochastic_data:
        assert isinstance(paramdata, _ParamData)
        if paramdata.is_constant():
            raise ValueError(
                "Stochastic data entry with name '%s' is not mutable. "
                "All stochastic data parameters must be initialized "
                "with the mutable keyword set to True."
                % (paramdata.name))
    return stochastic_data
Beispiel #8
0
def _map_variable_stages(model):

    variable_stage_annotation = locate_annotations(model,
                                                   VariableStageAnnotation,
                                                   max_allowed=1)
    if len(variable_stage_annotation) == 0:
        raise ValueError("Reference model is missing variable stage "
                         "annotation: %s" % (VariableStageAnnotation.__name__))
    else:
        assert len(variable_stage_annotation) == 1
        variable_stage_annotation = variable_stage_annotation[0][1]

    variable_stage_assignments = ComponentMap(
        variable_stage_annotation.expand_entries())
    if len(variable_stage_assignments) == 0:
        raise ValueError("At least one variable stage assignment "
                         "is required.")

    min_stagenumber = min(variable_stage_assignments.values(),
                          key=lambda x: x[0])[0]
    max_stagenumber = max(variable_stage_assignments.values(),
                          key=lambda x: x[0])[0]
    if max_stagenumber > 2:
        for var, (stagenum, derived) in \
              variable_stage_assignments.items():
            if stagenum > 2:
                raise ValueError(
                    "Embedded stochastic programs must be two-stage "
                    "(for now), but variable with name '%s' has been "
                    "annotated with stage number: %s" % (var.name, stagenum))

    stage_to_variables_map = {}
    stage_to_variables_map[1] = []
    stage_to_variables_map[2] = []
    for var in model.component_data_objects(
            Var,
            active=True,
            descend_into=True,
            sort=SortComponents.alphabetizeComponentAndIndex):
        stagenumber, derived = \
            variable_stage_assignments.get(var, (2, False))
        if (stagenumber != 1) and (stagenumber != 2):
            raise ValueError("Invalid stage annotation for variable with "
                             "name '%s'. Stage assignment must be 1 or 2. "
                             "Current value: %s" % (var.name, stagenumber))
        if (stagenumber == 1):
            stage_to_variables_map[1].append((var, derived))
        else:
            assert stagenumber == 2
            stage_to_variables_map[2].append((var, derived))

    variable_to_stage_map = ComponentMap()
    for stagenum, stagevars in stage_to_variables_map.items():
        for var, derived in stagevars:
            variable_to_stage_map[var] = (stagenum, derived)

    return (stage_to_variables_map, variable_to_stage_map,
            variable_stage_assignments)
Beispiel #9
0
def _convert_external_setup_without_cleanup(worker, scenario, output_directory,
                                            firststage_var_suffix,
                                            enforce_derived_nonanticipativity,
                                            io_options):
    import pyomo.environ
    assert os.path.exists(output_directory)

    io_options = dict(io_options)
    scenario_tree = worker.scenario_tree
    reference_model = scenario._instance
    rootnode = scenario_tree.findRootNode()
    firststage = scenario_tree.stages[0]
    secondstage = scenario_tree.stages[1]
    constraint_name_buffer = {}
    objective_name_buffer = {}
    variable_name_buffer = {}

    all_constraints = list(con
                           for con in reference_model.component_data_objects(
                               Constraint, active=True, descend_into=True))

    #
    # Check for model annotations
    #
    stochastic_rhs = locate_annotations(reference_model,
                                        StochasticConstraintBoundsAnnotation,
                                        max_allowed=1)
    if len(stochastic_rhs) == 0:
        stochastic_rhs = None
        stochastic_rhs_entries = {}
        empty_rhs_annotation = False
    else:
        assert len(stochastic_rhs) == 1
        stochastic_rhs = stochastic_rhs[0][1]
        if stochastic_rhs.has_declarations:
            empty_rhs_annotation = False
            stochastic_rhs_entries = stochastic_rhs.expand_entries()
            stochastic_rhs_entries.sort(
                key=lambda x: x[0].getname(True, constraint_name_buffer))
            if len(stochastic_rhs_entries) == 0:
                raise RuntimeError(
                    "The %s annotation was declared "
                    "with external entries but no active Constraint "
                    "objects were recovered from those entries." %
                    (StochasticConstraintBoundsAnnotation.__name__))
        else:
            empty_rhs_annotation = True
            stochastic_rhs_entries = tuple(
                (con, stochastic_rhs.default) for con in all_constraints)

    stochastic_matrix = locate_annotations(reference_model,
                                           StochasticConstraintBodyAnnotation,
                                           max_allowed=1)
    if len(stochastic_matrix) == 0:
        stochastic_matrix = None
        stochastic_matrix_entries = {}
        empty_matrix_annotation = False
    else:
        assert len(stochastic_matrix) == 1
        stochastic_matrix = stochastic_matrix[0][1]
        if stochastic_matrix.has_declarations:
            empty_matrix_annotation = False
            stochastic_matrix_entries = stochastic_matrix.expand_entries()
            stochastic_matrix_entries.sort(
                key=lambda x: x[0].getname(True, constraint_name_buffer))
            if len(stochastic_matrix_entries) == 0:
                raise RuntimeError(
                    "The %s annotation was declared "
                    "with external entries but no active Constraint "
                    "objects were recovered from those entries." %
                    (StochasticConstraintBoundsAnnotation.__name__))
        else:
            empty_matrix_annotation = True
            stochastic_matrix_entries = tuple(
                (con, stochastic_matrix.default) for con in all_constraints)

    stochastic_constraint_ids = set()
    stochastic_constraint_ids.update(
        id(con) for con, _ in stochastic_rhs_entries)
    stochastic_constraint_ids.update(
        id(con) for con, _ in stochastic_matrix_entries)

    stochastic_objective = locate_annotations(reference_model,
                                              StochasticObjectiveAnnotation,
                                              max_allowed=1)
    if len(stochastic_objective) == 0:
        stochastic_objective = None
    else:
        assert len(stochastic_objective) == 1
        stochastic_objective = stochastic_objective[0][1]

    stochastic_varbounds = locate_annotations(
        reference_model, StochasticVariableBoundsAnnotation)
    if len(stochastic_varbounds) > 0:
        raise ValueError(
            "The DDSIP writer does not currently support "
            "stochastic variable bounds. Invalid annotation type: %s" %
            (StochasticVariableBoundsAnnotation.__name__))

    if (stochastic_rhs is None) and \
       (stochastic_matrix is None) and \
       (stochastic_objective is None):
        raise RuntimeError("No stochastic annotations found. DDSIP "
                           "conversion requires at least one of the following "
                           "annotation types:\n - %s\n - %s\n - %s" %
                           (StochasticConstraintBoundsAnnotation.__name__,
                            StochasticConstraintBodyAnnotation.__name__,
                            StochasticObjectiveAnnotation.__name__))

    assert not hasattr(reference_model, "_repn")
    repn_cache = build_repns(reference_model)
    assert hasattr(reference_model, "_repn")
    assert not reference_model._gen_obj_repn
    assert not reference_model._gen_con_repn
    # compute values
    for block_repns in repn_cache.values():
        for repn in block_repns.values():
            repn.constant = value(repn.constant)
            repn.linear_coefs = [value(c) for c in repn.linear_coefs]
            repn.quadratic_coefs = [value(c) for c in repn.quadratic_coefs]

    #
    # Write the LP file once to obtain the symbol map
    #
    output_filename = os.path.join(output_directory,
                                   scenario.name + ".lp.setup")
    with WriterFactory("lp") as writer:
        assert 'column_order' not in io_options
        assert 'row_order' not in io_options
        output_fname, symbol_map = writer(reference_model, output_filename,
                                          lambda x: True, io_options)
        assert output_fname == output_filename
    _safe_remove_file(output_filename)

    StageToVariableMap = map_variable_stages(
        scenario,
        scenario_tree,
        symbol_map,
        enforce_derived_nonanticipativity=enforce_derived_nonanticipativity)
    firststage_variable_ids = \
        set(id(var) for symbol, var, scenario_tree_id
            in StageToVariableMap[firststage.name])
    secondstage_variable_ids = \
        set(id(var) for symbol, var, scenario_tree_id
            in StageToVariableMap[secondstage.name])

    StageToConstraintMap = \
        map_constraint_stages(
            scenario,
            scenario_tree,
            symbol_map,
            stochastic_constraint_ids,
            firststage_variable_ids,
            secondstage_variable_ids)
    secondstage_constraint_ids = \
        set(id(con) for symbols, con
            in StageToConstraintMap[secondstage.name])

    assert len(scenario_tree.stages) == 2
    firststage = scenario_tree.stages[0]
    secondstage = scenario_tree.stages[1]

    #
    # Make sure the objective references all first stage variables.
    # We do this by directly modifying the _repn of the
    # objective which the LP/MPS writer will reference next time we call
    # it. In addition, make sure that the first second-stage variable
    # in our column ordering also appears in the objective so that
    # ONE_VAR_CONSTANT does not get identified as the first
    # second-stage variable.
    # ** Just do NOT preprocess again until we call the writer **
    #
    objective_object = scenario._instance_objective
    assert objective_object is not None
    objective_block = objective_object.parent_block()
    objective_repn = repn_cache[id(objective_block)][objective_object]

    #
    # Create column (variable) ordering maps for LP/MPS files
    #
    column_order = ComponentMap()
    firststage_variable_count = 0
    secondstage_variable_count = 0
    # first-stage variables
    for column_index, (symbol, var, scenario_tree_id) \
        in enumerate(StageToVariableMap[firststage.name]):
        column_order[var] = column_index
        firststage_variable_count += 1
    # second-stage variables
    for column_index, (symbol, var, scenario_tree_id) \
        in enumerate(StageToVariableMap[secondstage.name],
                     len(column_order)):
        column_order[var] = column_index
        secondstage_variable_count += 1
    # account for the ONE_VAR_CONSTANT second-stage variable
    # added by the LP writer
    secondstage_variable_count += 1

    #
    # Create row (constraint) ordering maps for LP/MPS files
    #
    firststage_constraint_count = 0
    secondstage_constraint_count = 0
    row_order = ComponentMap()
    # first-stage constraints
    for row_index, (symbols, con) \
        in enumerate(StageToConstraintMap[firststage.name]):
        row_order[con] = row_index
        firststage_constraint_count += len(symbols)
    # second-stage constraints
    for row_index, (symbols, con) \
        in enumerate(StageToConstraintMap[secondstage.name],
                     len(row_order)):
        row_order[con] = row_index
        secondstage_constraint_count += len(symbols)
    # account for the ONE_VAR_CONSTANT = 1 second-stage constraint
    # added by the LP writer
    secondstage_constraint_count += 1

    #
    # Create a custom labeler that allows DDSIP to identify
    # first-stage variables
    #
    if io_options.pop('symbolic_solver_labels', False):
        _labeler = TextLabeler()
    else:
        _labeler = NumericLabeler('x')
    labeler = lambda x: _labeler(x) + \
              (""
               if ((not isinstance(x, _VarData)) or \
                   (id(x) not in firststage_variable_ids)) else \
               firststage_var_suffix)

    #
    # Write the ordered LP/MPS file
    #
    output_filename = os.path.join(output_directory, scenario.name + ".lp")
    symbols_filename = os.path.join(output_directory,
                                    scenario.name + ".lp.symbols")
    with WriterFactory("lp") as writer:
        assert 'column_order' not in io_options
        assert 'row_order' not in io_options
        assert 'labeler' not in io_options
        assert 'force_objective_constant' not in io_options
        io_options['column_order'] = column_order
        io_options['row_order'] = row_order
        io_options['force_objective_constant'] = True
        io_options['labeler'] = labeler
        output_fname, symbol_map = writer(reference_model, output_filename,
                                          lambda x: True, io_options)
        assert output_fname == output_filename
        # write the lp file symbol paired with the scenario
        # tree id for each variable in the root node
        with open(symbols_filename, "w") as f:
            st_symbol_map = reference_model._ScenarioTreeSymbolMap
            lines = []
            for id_ in sorted(rootnode._variable_ids):
                var = st_symbol_map.bySymbol[id_]
                if not var.is_expression_type():
                    lp_label = symbol_map.byObject[id(var)]
                    lines.append("%s %s\n" % (lp_label, id_))
            f.writelines(lines)

    # re-generate these maps as the LP/MPS symbol map
    # is likely different
    StageToVariableMap = map_variable_stages(
        scenario,
        scenario_tree,
        symbol_map,
        enforce_derived_nonanticipativity=enforce_derived_nonanticipativity)

    StageToConstraintMap = map_constraint_stages(scenario, scenario_tree,
                                                 symbol_map,
                                                 stochastic_constraint_ids,
                                                 firststage_variable_ids,
                                                 secondstage_variable_ids)

    # generate a few data structures that are used
    # when writing the .sc files
    constraint_symbols = ComponentMap(
        (con, symbols) for stage_name in StageToConstraintMap
        for symbols, con in StageToConstraintMap[stage_name])

    #
    # Write the body of the .sc files
    #
    modified_constraint_lb = ComponentMap()
    modified_constraint_ub = ComponentMap()

    #
    # Stochastic RHS
    #
    # **NOTE: In the code that follows we assume the LP
    #         writer always moves constraint body
    #         constants to the rhs and that the lower part
    #         of any range constraints are written before
    #         the upper part.
    #
    stochastic_rhs_count = 0
    with open(os.path.join(output_directory, scenario.name + ".rhs.sc.struct"),
              'w') as f_rhs_struct:
        with open(os.path.join(output_directory, scenario.name + ".rhs.sc"),
                  'w') as f_rhs:
            scenario_probability = scenario.probability
            rhs_struct_template = " %s\n"
            rhs_template = "  %.17g\n"
            f_rhs.write("scen\n%.17g\n" %
                        (_no_negative_zero(scenario_probability)))
            if stochastic_rhs is not None:
                for con, include_bound in stochastic_rhs_entries:
                    assert isinstance(con, _ConstraintData)
                    if not empty_rhs_annotation:
                        # verify that this constraint was
                        # flagged by PySP or the user as second-stage
                        if id(con) not in secondstage_constraint_ids:
                            raise RuntimeError(
                                "The constraint %s has been declared "
                                "in the %s annotation but it was not identified as "
                                "a second-stage constraint. To correct this issue, "
                                "remove the constraint from this annotation." %
                                (con.name,
                                 StochasticConstraintBoundsAnnotation.__name__)
                            )

                    constraint_repn = \
                        repn_cache[id(con.parent_block())][con]
                    if not constraint_repn.is_linear():
                        raise RuntimeError(
                            "Only linear constraints are "
                            "accepted for conversion to DDSIP format. "
                            "Constraint %s is not linear." % (con.name))

                    body_constant = constraint_repn.constant
                    # We are going to rewrite the core problem file
                    # with all stochastic values set to zero. This will
                    # allow an easy test for missing user annotations.
                    constraint_repn.constant = 0
                    if body_constant is None:
                        body_constant = 0.0
                    symbols = constraint_symbols[con]
                    assert len(symbols) > 0
                    for con_label in symbols:
                        if con_label.startswith('c_e_') or \
                           con_label.startswith('c_l_'):
                            assert (include_bound is True) or \
                                   (include_bound[0] is True)
                            stochastic_rhs_count += 1
                            f_rhs_struct.write(rhs_struct_template %
                                               (con_label))
                            f_rhs.write(rhs_template %
                                        (_no_negative_zero(
                                            value(con.lower) - \
                                            value(body_constant))))
                            # We are going to rewrite the core problem file
                            # with all stochastic values set to zero. This will
                            # allow an easy test for missing user annotations.
                            modified_constraint_lb[con] = con.lower
                            con._lower = _deterministic_check_constant
                            if con_label.startswith('c_e_'):
                                modified_constraint_ub[con] = con.upper
                                con._upper = _deterministic_check_constant
                        elif con_label.startswith('r_l_'):
                            if (include_bound is True) or \
                               (include_bound[0] is True):
                                stochastic_rhs_count += 1
                                f_rhs_struct.write(rhs_struct_template %
                                                   (con_label))
                                f_rhs.write(rhs_template %
                                             (_no_negative_zero(
                                                 value(con.lower) - \
                                                 value(body_constant))))
                                # We are going to rewrite the core problem file
                                # with all stochastic values set to zero. This will
                                # allow an easy test for missing user annotations.
                                modified_constraint_lb[con] = con.lower
                                con._lower = _deterministic_check_constant
                        elif con_label.startswith('c_u_'):
                            assert (include_bound is True) or \
                                   (include_bound[1] is True)
                            stochastic_rhs_count += 1
                            f_rhs_struct.write(rhs_struct_template %
                                               (con_label))
                            f_rhs.write(rhs_template %
                                        (_no_negative_zero(
                                            value(con.upper) - \
                                            value(body_constant))))
                            # We are going to rewrite the core problem file
                            # with all stochastic values set to zero. This will
                            # allow an easy test for missing user annotations.
                            modified_constraint_ub[con] = con.upper
                            con._upper = _deterministic_check_constant
                        elif con_label.startswith('r_u_'):
                            if (include_bound is True) or \
                               (include_bound[1] is True):
                                stochastic_rhs_count += 1
                                f_rhs_struct.write(rhs_struct_template %
                                                   (con_label))
                                f_rhs.write(rhs_template %
                                            (_no_negative_zero(
                                                value(con.upper) - \
                                                value(body_constant))))
                                # We are going to rewrite the core problem file
                                # with all stochastic values set to zero. This will
                                # allow an easy test for missing user annotations.
                                modified_constraint_ub[con] = con.upper
                                con._upper = _deterministic_check_constant
                        else:
                            assert False

    #
    # Stochastic Matrix
    #
    stochastic_matrix_count = 0
    with open(
            os.path.join(output_directory,
                         scenario.name + ".matrix.sc.struct"),
            'w') as f_mat_struct:
        with open(os.path.join(output_directory, scenario.name + ".matrix.sc"),
                  'w') as f_mat:
            scenario_probability = scenario.probability
            matrix_struct_template = " %s %s\n"
            matrix_template = "  %.17g\n"
            f_mat.write("scen\n")
            if stochastic_matrix is not None:
                for con, var_list in stochastic_matrix_entries:
                    assert isinstance(con, _ConstraintData)
                    if not empty_matrix_annotation:
                        # verify that this constraint was
                        # flagged by PySP or the user as second-stage
                        if id(con) not in secondstage_constraint_ids:
                            raise RuntimeError(
                                "The constraint %s has been declared "
                                "in the %s annotation but it was not identified as "
                                "a second-stage constraint. To correct this issue, "
                                "remove the constraint from this annotation." %
                                (con.name,
                                 StochasticConstraintBodyAnnotation.__name__))
                    constraint_repn = \
                        repn_cache[id(con.parent_block())][con]
                    if not constraint_repn.is_linear():
                        raise RuntimeError(
                            "Only linear constraints are "
                            "accepted for conversion to DDSIP format. "
                            "Constraint %s is not linear." % (con.name))
                    assert len(constraint_repn.linear_vars) > 0
                    if var_list is None:
                        var_list = constraint_repn.linear_vars
                    assert len(var_list) > 0
                    symbols = constraint_symbols[con]
                    # sort the variable list by the column ordering
                    # so that we have deterministic output
                    var_list = list(var_list)
                    var_list.sort(key=lambda _v: column_order[_v])
                    new_coefs = list(constraint_repn.linear_coefs)
                    for var in var_list:
                        assert isinstance(var, _VarData)
                        assert not var.fixed
                        var_coef = None
                        for i, (_var, coef) in enumerate(
                                zip(constraint_repn.linear_vars,
                                    constraint_repn.linear_coefs)):
                            if _var is var:
                                var_coef = coef
                                # We are going to rewrite with core problem file
                                # with all stochastic values set to zero. This will
                                # allow an easy test for missing user annotations.
                                new_coefs[i] = _deterministic_check_value
                                break
                        if var_coef is None:
                            raise RuntimeError(
                                "The coefficient for variable %s has "
                                "been marked as stochastic in constraint %s using "
                                "the %s annotation, but the variable does not appear"
                                " in the canonical constraint expression." %
                                (var.name, con.name,
                                 StochasticConstraintBodyAnnotation.__name__))
                        var_label = symbol_map.byObject[id(var)]
                        for con_label in symbols:
                            stochastic_matrix_count += 1
                            f_mat_struct.write(matrix_struct_template %
                                               (con_label, var_label))
                            f_mat.write(matrix_template %
                                        (_no_negative_zero(value(var_coef))))

                    constraint_repn.linear_coefs = tuple(new_coefs)

    #
    # Stochastic Objective
    #
    stochastic_cost_count = 0
    with open(
            os.path.join(output_directory, scenario.name + ".cost.sc.struct"),
            'w') as f_obj_struct:
        with open(os.path.join(output_directory, scenario.name + ".cost.sc"),
                  'w') as f_obj:
            obj_struct_template = " %s\n"
            obj_template = "  %.17g\n"
            f_obj.write("scen\n")
            if stochastic_objective is not None:
                if stochastic_objective.has_declarations:
                    sorted_values = stochastic_objective.expand_entries()
                    assert len(sorted_values) <= 1
                    if len(sorted_values) == 0:
                        raise RuntimeError(
                            "The %s annotation was declared "
                            "with external entries but no active Objective "
                            "objects were recovered from those entries." %
                            (StochasticObjectiveAnnotation.__name__))
                    obj, (objective_variables, include_constant) = \
                        sorted_values[0]
                    assert obj is objective_object
                else:
                    objective_variables, include_constant = \
                        stochastic_objective.default

                if not objective_repn.is_linear():
                    raise RuntimeError(
                        "Only linear stochastic objectives are "
                        "accepted for conversion to DDSIP format. "
                        "Objective %s is not linear." %
                        (objective_object.name))
                if objective_variables is None:
                    objective_variables = objective_repn.linear_vars
                stochastic_objective_label = symbol_map.byObject[id(
                    objective_object)]
                # sort the variable list by the column ordering
                # so that we have deterministic output
                objective_variables = list(objective_variables)
                objective_variables.sort(key=lambda _v: column_order[_v])
                assert (len(objective_variables) > 0) or include_constant
                new_coefs = list(objective_repn.linear_coefs)
                for var in objective_variables:
                    assert isinstance(var, _VarData)
                    var_coef = None
                    for i, (_var, coef) in enumerate(
                            zip(objective_repn.linear_vars,
                                objective_repn.linear_coefs)):
                        if _var is var:
                            var_coef = coef
                            # We are going to rewrite the core problem file
                            # with all stochastic values set to zero. This will
                            # allow an easy test for missing user annotations.
                            new_coefs[i] = _deterministic_check_value
                            break
                    if var_coef is None:
                        raise RuntimeError(
                            "The coefficient for variable %s has "
                            "been marked as stochastic in objective %s using "
                            "the %s annotation, but the variable does not appear"
                            " in the canonical objective expression." %
                            (var.name, objective_object.name,
                             StochasticObjectiveAnnotation.__name__))
                    var_label = symbol_map.byObject[id(var)]
                    stochastic_cost_count += 1
                    f_obj_struct.write(obj_struct_template % (var_label))
                    f_obj.write(obj_template %
                                (_no_negative_zero(value(var_coef))))

                objective_repn.linear_coefs = tuple(new_coefs)
                if include_constant:
                    obj_constant = objective_repn.constant
                    # We are going to rewrite the core problem file
                    # with all stochastic values set to zero. This will
                    # allow an easy test for missing user annotations.
                    objective_repn.constant = _deterministic_check_value
                    if obj_constant is None:
                        obj_constant = 0.0
                    stochastic_cost_count += 1
                    f_obj_struct.write(obj_struct_template %
                                       ("ONE_VAR_CONSTANT"))
                    f_obj.write(obj_template %
                                (_no_negative_zero(obj_constant)))

    #
    # Write the deterministic part of the LP/MPS-file to its own
    # file for debugging purposes
    #
    reference_model_name = reference_model.name
    reference_model._name = "ZeroStochasticData"
    det_output_filename = os.path.join(output_directory,
                                       scenario.name + ".lp.det")
    with WriterFactory("lp") as writer:
        output_fname, symbol_map = writer(reference_model, det_output_filename,
                                          lambda x: True, io_options)
        assert output_fname == det_output_filename
    reference_model._name = reference_model_name

    # reset bounds on any constraints that were modified
    for con, lower in iteritems(modified_constraint_lb):
        con._lower = as_numeric(lower)
    for con, upper in iteritems(modified_constraint_ub):
        con._upper = as_numeric(upper)

    return (firststage_variable_count, secondstage_variable_count,
            firststage_constraint_count, secondstage_constraint_count,
            stochastic_cost_count, stochastic_rhs_count,
            stochastic_matrix_count)
Beispiel #10
0
    def __init__(self, reference_model):

        self.reference_model = None
        self.objective = None
        self.time_stages = None

        self.stage_to_variables_map = {}
        self.variable_to_stage_map = {}

        # the set of stochastic data objects
        # (possibly mapped to some distribution)
        self.stochastic_data = None

        # maps between variables and objectives
        self.variable_to_objectives_map = ComponentMap()
        self.objective_to_variables_map = ComponentMap()

        # maps between variables and constraints
        self.variable_to_constraints_map = ComponentMap()
        self.constraint_to_variables_map = ComponentMap()

        # maps between stochastic data and objectives
        self.stochastic_data_to_objectives_map = ComponentMap()
        self.objective_to_stochastic_data_map = ComponentMap()

        # maps between stochastic data and constraints
        self.stochastic_data_to_constraints_map = ComponentMap()
        self.constraint_to_stochastic_data_map = ComponentMap()

        # maps between stochastic data and variable lower and upper bounds
        self.stochastic_data_to_variables_lb_map = ComponentMap()
        self.variable_to_stochastic_data_lb_map = ComponentMap()

        self.stochastic_data_to_variables_ub_map = ComponentMap()
        self.variable_to_stochastic_data_ub_map = ComponentMap()

        self.variable_symbols = ComponentMap()

        if not isinstance(reference_model, Block):
            raise TypeError("reference model input must be a Pyomo model")
        self.reference_model = reference_model

        #
        # Extract stochastic parameters from the
        # StochasticDataAnnotation object
        #
        self.stochastic_data = \
            _extract_stochastic_data(self.reference_model)

        #
        # Get the variable stages from the
        # VariableStageAnnotation object
        #
        (self.stage_to_variables_map,
         self.variable_to_stage_map,
         self._variable_stage_assignments) = \
            _map_variable_stages(self.reference_model)
        self.time_stages = tuple(sorted(self.stage_to_variables_map))
        assert self.time_stages[0] == 1
        self.variable_symbols = generate_cuid_names(self.reference_model,
                                                    ctype=Var)
        # remove the parent blocks from this map
        keys_to_delete = []
        for var in self.variable_symbols:
            if var.parent_component().ctype is not Var:
                keys_to_delete.append(var)
        for key in keys_to_delete:
            del self.variable_symbols[key]

        #
        # Get the stage cost components from the StageCostAnnotation
        # and generate a dummy single-scenario scenario tree
        #
        stage_cost_annotation = locate_annotations(self.reference_model,
                                                   StageCostAnnotation,
                                                   max_allowed=1)
        if len(stage_cost_annotation) == 0:
            raise ValueError("Reference model is missing stage cost "
                             "annotation: %s" % (StageCostAnnotation.__name__))
        else:
            assert len(stage_cost_annotation) == 1
            stage_cost_annotation = stage_cost_annotation[0][1]
        stage_cost_assignments = ComponentMap(
            stage_cost_annotation.expand_entries())

        stage1_cost = None
        stage2_cost = None
        for cdata, stagenum in stage_cost_assignments.items():
            if stagenum == 1:
                stage1_cost = cdata
            elif stagenum == 2:
                stage2_cost = cdata
        if stage1_cost is None:
            raise ValueError("Missing stage cost annotation "
                             "for time stage: 1")
        if stage2_cost is None:
            raise ValueError("Missing stage cost annotation "
                             "for time stage: 2")
        assert stage1_cost is not stage2_cost
        self._stage1_cost = stage1_cost
        self._stage2_cost = stage2_cost

        #
        # Extract the locations of variables and stochastic data
        # within the model
        #
        sto_obj = StochasticObjectiveAnnotation()
        for objcntr, obj in enumerate(
                self.reference_model.component_data_objects(Objective,
                                                            active=True,
                                                            descend_into=True),
                1):

            if objcntr > 1:
                raise ValueError(
                    "Reference model can not contain more than one "
                    "active objective")

            self.objective = obj
            self.objective_sense = obj.sense

            obj_params = tuple(
                self._collect_mutable_parameters(obj.expr).values())
            self.objective_to_stochastic_data_map[obj] = []
            for paramdata in obj_params:
                if paramdata in self.stochastic_data:
                    self.stochastic_data_to_objectives_map.\
                        setdefault(paramdata, []).append(obj)
                    self.objective_to_stochastic_data_map[obj].\
                        append(paramdata)
            if len(self.objective_to_stochastic_data_map[obj]) == 0:
                del self.objective_to_stochastic_data_map[obj]
            else:
                # TODO: Can we make this declaration sparse
                #       by identifying which variables have
                #       stochastic coefficients? How to handle
                #       non-linear expressions?
                sto_obj.declare(obj)

            obj_variables = tuple(self._collect_variables(obj.expr).values())
            self.objective_to_variables_map[obj] = []
            for var in obj_variables:
                self.variable_to_objectives_map.\
                    setdefault(var, []).append(obj)
                self.objective_to_variables_map[obj].append(var)
            if len(self.objective_to_variables_map[obj]) == 0:
                del self.objective_to_variables_map[obj]

        sto_conbounds = StochasticConstraintBoundsAnnotation()
        sto_conbody = StochasticConstraintBodyAnnotation()
        for con in self.reference_model.component_data_objects(
                Constraint, active=True, descend_into=True):

            lower_params = tuple(
                self._collect_mutable_parameters(con.lower).values())
            body_params = tuple(
                self._collect_mutable_parameters(con.body).values())
            upper_params = tuple(
                self._collect_mutable_parameters(con.upper).values())

            # TODO: Can we make this declaration sparse
            #       by idenfifying which variables have
            #       stochastic coefficients? How to handle
            #       non-linear expressions? Currently, this
            #       code also fails to detect that mutable
            #       "constant" expressions might fall out
            #       of the body and into the bounds.
            if len(body_params):
                sto_conbody.declare(con)
            if len(body_params) or \
               len(lower_params) or \
               len(upper_params):
                sto_conbounds.declare(
                    con,
                    lb=bool(len(lower_params) or len(body_params)),
                    ub=bool(len(upper_params) or len(body_params)))

            all_stochastic_params = {}
            for param in itertools.chain(lower_params, body_params,
                                         upper_params):
                if param in self.stochastic_data:
                    all_stochastic_params[id(param)] = param

            if len(all_stochastic_params) > 0:
                self.constraint_to_stochastic_data_map[con] = []
                # no params will appear twice in this iteration
                for param in all_stochastic_params.values():
                    self.stochastic_data_to_constraints_map.\
                        setdefault(param, []).append(con)
                    self.constraint_to_stochastic_data_map[con].\
                        append(param)

            body_variables = tuple(self._collect_variables(con.body).values())
            self.constraint_to_variables_map[con] = []
            for var in body_variables:
                self.variable_to_constraints_map.\
                    setdefault(var, []).append(con)
                self.constraint_to_variables_map[con].append(var)

        # For now, it is okay to have SOSConstraints in the
        # representation of a problem, but the SOS
        # constraints can't have custom weights that
        # represent stochastic data
        for soscon in self.reference_model.component_data_objects(
                SOSConstraint, active=True, descend_into=True):
            for var, weight in soscon.get_items():
                weight_params = tuple(
                    self._collect_mutable_parameters(weight).values())
                if param in self.stochastic_data:
                    raise ValueError(
                        "SOSConstraints with stochastic data are currently"
                        " not supported in embedded stochastic programs. "
                        "The SOSConstraint component '%s' has a weight "
                        "term for variable '%s' that references stochastic"
                        " parameter '%s'" %
                        (soscon.name, var.name, param.name))
                self.variable_to_constraints_map.\
                    setdefault(var, []).append(soscon)
                self.constraint_to_variables_map.\
                    setdefault(soscon, []).append(var)

        sto_varbounds = StochasticVariableBoundsAnnotation()
        for var in self.reference_model.component_data_objects(
                Var, descend_into=True):

            lower_params = tuple(
                self._collect_mutable_parameters(var.lb).values())
            upper_params = tuple(
                self._collect_mutable_parameters(var.ub).values())

            if (len(lower_params) > 0) or \
               (len(upper_params) > 0):
                sto_varbounds.declare(var,
                                      lb=bool(len(lower_params) > 0),
                                      ub=bool(len(upper_params) > 0))

            self.variable_to_stochastic_data_lb_map[var] = []
            for param in lower_params:
                if param in self.stochastic_data:
                    self.stochastic_data_to_variables_lb_map.\
                        setdefault(param, []).append(var)
                    self.variable_to_stochastic_data_lb_map[var].\
                        append(param)
            if len(self.variable_to_stochastic_data_lb_map[var]) == 0:
                del self.variable_to_stochastic_data_lb_map[var]

            self.variable_to_stochastic_data_ub_map[var] = []
            for param in upper_params:
                if param in self.stochastic_data:
                    self.stochastic_data_to_variables_ub_map.\
                        setdefault(param, []).append(var)
                    self.variable_to_stochastic_data_ub_map[var].\
                        append(param)
            if len(self.variable_to_stochastic_data_ub_map[var]) == 0:
                del self.variable_to_stochastic_data_ub_map[var]

        #
        # Generate the explicit annotations
        #

        # first make sure these annotations do not already exist
        if len(
                locate_annotations(self.reference_model,
                                   StochasticConstraintBoundsAnnotation)) > 0:
            raise ValueError(
                "Reference model can not contain "
                "a StochasticConstraintBoundsAnnotation declaration.")
        if len(
                locate_annotations(self.reference_model,
                                   StochasticConstraintBodyAnnotation)) > 0:
            raise ValueError(
                "Reference model can not contain "
                "a StochasticConstraintBodyAnnotation declaration.")
        if len(
                locate_annotations(self.reference_model,
                                   StochasticObjectiveAnnotation)) > 0:
            raise ValueError("Reference model can not contain "
                             "a StochasticObjectiveAnnotation declaration.")

        # now add any necessary annotations
        if sto_obj.has_declarations:
            assert not hasattr(
                self.reference_model,
                ".pyspembeddedsp_stochastic_objective_annotation")
            setattr(self.reference_model,
                    ".pyspembeddedsp_stochastic_objective_annotation", sto_obj)
        if sto_conbody.has_declarations:
            assert not hasattr(
                self.reference_model,
                ".pyspembeddedsp_stochastic_constraint_body_annotation")
            setattr(self.reference_model,
                    ".pyspembeddedsp_stochastic_constraint_body_annotation",
                    sto_conbody)
        if sto_conbounds.has_declarations:
            assert not hasattr(
                self.reference_model,
                ".pyspembeddedsp_stochastic_constraint_bounds_annotation")
            setattr(self.reference_model,
                    ".pyspembeddedsp_stochastic_constraint_bounds_annotation",
                    sto_conbounds)
        if sto_varbounds.has_declarations:
            assert not hasattr(
                self.reference_model,
                ".pyspembeddedsp_stochastic_variable_bounds_annotation")
            setattr(self.reference_model,
                    ".pyspembeddedsp_stochastic_variable_bounds_annotation",
                    sto_varbounds)
Beispiel #11
0
 def __init__(self):
     assert len(self._ctypes) > 0
     assert len(self._ctypes) == len(self._ctypes_data)
     self._data = ComponentMap()
     self._default = None
Beispiel #12
0
class PySP_Annotation(object):
    _ctypes = ()
    _ctypes_data = ()

    def __init__(self):
        assert len(self._ctypes) > 0
        assert len(self._ctypes) == len(self._ctypes_data)
        self._data = ComponentMap()
        self._default = None

    @property
    def default(self):
        return self._default

    def has_declarations(self):
        return bool(len(self._data) > 0)

    def declare(self, component, *args, **kwds):
        if (
            isinstance(component, self._ctypes_data)
            or isinstance(component, self._ctypes)
            or isinstance(component, (Block, _BlockData))
        ):
            self._declare_impl(component, *args, **kwds)
        else:
            raise TypeError(
                "Declarations in annotation type %s must be of types "
                "%s or Block. Invalid type: %s"
                % (self.__class__.__name__, (",".join(ctype.__name__ for ctype in self._ctypes)), type(component))
            )

    def pprint(self, *args, **kwds):
        self._data.pprint(*args, **kwds)

    def expand_entries(self, expand_containers=True):
        """
        Translates the annotation into a flattened list of
        (component, annotation_value) pairs. The ctypes argument
        can be a single component type are a tuple of component
        types. If any components are found in the annotation
        not matching those types, an exception will be
        raised. If 'expand_containers' is set to False, then
        component containers will not be flattened into the set
        of components they contain.
        """
        items = []
        component_ids = set()

        def _append(component, val):
            items.append((component, val))
            if id(component) in component_ids:
                raise RuntimeError(
                    "Component %s was assigned multiple declarations "
                    "in annotation type %s. To correct this issue, ensure that "
                    "multiple container components under which the component might "
                    "be stored (such as a Block and an indexed Constraint) are not "
                    "simultaneously set in this annotation." % (component.name, self.__class__.__name__)
                )
            component_ids.add(id(component))

        for component in self._data:
            component_annotation_value = self._data[component]
            if not getattr(component, "active", True):
                continue
            if isinstance(component, self._ctypes_data):
                _append(component, component_annotation_value)
            elif isinstance(component, self._ctypes):
                if expand_containers:
                    for index in component:
                        obj = component[index]
                        if getattr(obj, "active", True):
                            _append(obj, component_annotation_value)
                else:
                    _append(component, component_annotation_value)
            elif isinstance(component, _BlockData):
                for ctype in self._ctypes:
                    if expand_containers:
                        for obj in component.component_data_objects(ctype, active=True, descend_into=True):
                            _append(obj, component_annotation_value)
                    else:
                        for obj in component.component_data_objects(ctype, active=True, descend_into=True):
                            _append(obj, component_annotation_value)
            elif isinstance(component, Block):
                for index in component:
                    block = component[index]
                    if block.active:
                        for ctype in self._ctypes:
                            if expand_containers:
                                for obj in block.component_data_objects(ctype, active=True, descend_into=True):
                                    _append(obj, component_annotation_value)
                            else:
                                for obj in block.component_objects(ctype, active=True, descend_into=True):
                                    _append(obj, component_annotation_value)
            else:
                raise TypeError(
                    "Declarations in annotation type %s must be of types "
                    "%s or Block. Invalid type: %s"
                    % (self.__class__.__name__, (",".join(ctype.__name__ for ctype in self._ctypes)), type(component))
                )

        return items
Beispiel #13
0
 def __init__(self):
     assert len(self._ctypes) > 0
     assert len(self._ctypes) == len(self._ctypes_data)
     self._data = ComponentMap()
     self._default = None
Beispiel #14
0
class PySP_Annotation(object):
    _ctypes = ()
    _ctypes_data = ()

    def __init__(self):
        assert len(self._ctypes) > 0
        assert len(self._ctypes) == len(self._ctypes_data)
        self._data = ComponentMap()
        self._default = None

    @property
    def default(self):
        return self._default

    def has_declarations(self):
        return bool(len(self._data) > 0)

    def declare(self, component, *args, **kwds):
        if isinstance(component, self._ctypes_data) or \
           isinstance(component, self._ctypes) or \
           isinstance(component, (Block, _BlockData)):
            self._declare_impl(component, *args, **kwds)
        else:
            raise TypeError(
                "Declarations in annotation type %s must be of types "
                "%s or Block. Invalid type: %s" %
                (self.__class__.__name__,
                 (",".join(ctype.__name__
                           for ctype in self._ctypes)), type(component)))

    def pprint(self, *args, **kwds):
        self._data.pprint(*args, **kwds)

    def expand_entries(self, expand_containers=True):
        """
        Translates the annotation into a flattened list of
        (component, annotation_value) pairs. The ctypes argument
        can be a single component type are a tuple of component
        types. If any components are found in the annotation
        not matching those types, an exception will be
        raised. If 'expand_containers' is set to False, then
        component containers will not be flattened into the set
        of components they contain.
        """
        items = []
        component_ids = set()

        def _append(component, val):
            items.append((component, val))
            if id(component) in component_ids:
                raise RuntimeError(
                    "Component %s was assigned multiple declarations "
                    "in annotation type %s. To correct this issue, ensure that "
                    "multiple container components under which the component might "
                    "be stored (such as a Block and an indexed Constraint) are not "
                    "simultaneously set in this annotation." %
                    (component.name, self.__class__.__name__))
            component_ids.add(id(component))

        for component in self._data:
            component_annotation_value = self._data[component]
            if not getattr(component, "active", True):
                continue
            if isinstance(component, self._ctypes_data):
                _append(component, component_annotation_value)
            elif isinstance(component, self._ctypes):
                if expand_containers:
                    for index in component:
                        obj = component[index]
                        if getattr(obj, "active", True):
                            _append(obj, component_annotation_value)
                else:
                    _append(component, component_annotation_value)
            elif isinstance(component, _BlockData):
                for ctype in self._ctypes:
                    if expand_containers:
                        for obj in component.component_data_objects(
                                ctype, active=True, descend_into=True):
                            _append(obj, component_annotation_value)
                    else:
                        for obj in component.component_data_objects(
                                ctype, active=True, descend_into=True):
                            _append(obj, component_annotation_value)
            elif isinstance(component, Block):
                for index in component:
                    block = component[index]
                    if block.active:
                        for ctype in self._ctypes:
                            if expand_containers:
                                for obj in block.component_data_objects(
                                        ctype, active=True, descend_into=True):
                                    _append(obj, component_annotation_value)
                            else:
                                for obj in block.component_objects(
                                        ctype, active=True, descend_into=True):
                                    _append(obj, component_annotation_value)
            else:
                raise TypeError(
                    "Declarations in annotation type %s must be of types "
                    "%s or Block. Invalid type: %s" %
                    (self.__class__.__name__,
                     (",".join(ctype.__name__
                               for ctype in self._ctypes)), type(component)))

        return items
Beispiel #15
0
    def __init__(self, reference_model):

        self.reference_model = None
        self.objective = None
        self.scenario_tree = None

        self.stage_to_variables_map = {}
        self.variable_to_stage_map = {}

        # the set of stochastic data objects
        # (possibly mapped to some distribution)
        self.stochastic_data = None

        # maps between variables and objectives
        self.variable_to_objectives_map = ComponentMap()
        self.objective_to_variables_map = ComponentMap()

        # maps between variables and constraints
        self.variable_to_constraints_map = ComponentMap()
        self.constraint_to_variables_map = ComponentMap()

        # maps between stochastic data and objectives
        self.stochastic_data_to_objectives_map = ComponentMap()
        self.objective_to_stochastic_data_map = ComponentMap()

        # maps between stochastic data and constraints
        self.stochastic_data_to_constraints_map = ComponentMap()
        self.constraint_to_stochastic_data_map = ComponentMap()

        # maps between stochastic data and variables lower and upper bounds
        self.stochastic_data_to_variables_lb_map = ComponentMap()
        self.variable_to_stochastic_data_lb_map = ComponentMap()

        self.stochastic_data_to_variables_ub_map = ComponentMap()
        self.variable_to_stochastic_data_ub_map = ComponentMap()

        if not isinstance(reference_model, Block):
            raise TypeError("reference model input must be a Pyomo model")
        self.reference_model = reference_model

        #
        # Extract stochastic parameters from the PySP_StochasticDataAnnotation object
        #

        self.stochastic_data = \
            self._extract_stochastic_data(self.reference_model)

        #
        # Get the variable stages from the PySP_VariableStageAnnotation object
        #

        (self.stage_to_variables_map,
         self.variable_to_stage_map,
         variable_stage_assignments) = \
            self._map_variable_stages(self.reference_model)

        #
        # Get the stage cost components from the PySP_StageCostAnnotation
        # and generate a dummy single-scenario scenario tree
        #

        self.scenario_tree = \
            self._generate_base_scenario_tree(self.reference_model,
                                              variable_stage_assignments)

        #
        # Extract the locations of variables and stochastic data
        # within the model
        #

        for objcntr, objdata in enumerate(
                self.reference_model.component_data_objects(Objective,
                                                            active=True,
                                                            descend_into=True),
                1):

            if objcntr > 1:
                raise ValueError(
                    "Reference model can not contain more than one "
                    "active objective")

            self.objective = objdata
            self.objective_sense = objdata.sense

            obj_params = \
                tuple(self._collect_mutable_parameters(objdata.expr).values())
            self.objective_to_stochastic_data_map[objdata] = []
            for paramdata in obj_params:
                if paramdata in self.stochastic_data:
                    self.stochastic_data_to_objectives_map.\
                        setdefault(paramdata, []).append(objdata)
                    self.objective_to_stochastic_data_map[objdata].\
                        append(paramdata)
            if len(self.objective_to_stochastic_data_map[objdata]) == 0:
                del self.objective_to_stochastic_data_map[objdata]

            obj_variables = \
                tuple(self._collect_unfixed_variables(objdata.expr).values())
            self.objective_to_variables_map[objdata] = []
            for vardata in obj_variables:
                self.variable_to_objectives_map.\
                    setdefault(vardata, []).append(objdata)
                self.objective_to_variables_map[objdata].append(vardata)
            if len(self.objective_to_variables_map[objdata]) == 0:
                del self.objective_to_variables_map[objdata]

        for condata in self.reference_model.component_data_objects(
                Constraint, active=True, descend_into=True):

            lower_params = \
                tuple(self._collect_mutable_parameters(condata.lower).values())
            body_params = \
                tuple(self._collect_mutable_parameters(condata.body).values())
            upper_params = \
                tuple(self._collect_mutable_parameters(condata.upper).values())

            all_stochastic_params = {}
            for paramdata in itertools.chain(lower_params, body_params,
                                             upper_params):
                if paramdata in self.stochastic_data:
                    all_stochastic_params[id(paramdata)] = paramdata

            if len(all_stochastic_params) > 0:
                self.constraint_to_stochastic_data_map[condata] = []
                # no params will appear twice in this iteration
                for paramdata in all_stochastic_params.values():
                    self.stochastic_data_to_constraints_map.\
                        setdefault(paramdata, []).append(condata)
                    self.constraint_to_stochastic_data_map[condata].\
                        append(paramdata)

            body_variables = \
                tuple(self._collect_unfixed_variables(condata.body).values())
            self.constraint_to_variables_map[condata] = []
            for vardata in body_variables:
                self.variable_to_constraints_map.\
                    setdefault(vardata, []).append(condata)
                self.constraint_to_variables_map[condata].append(vardata)

        # For now, it is okay to have SOSConstraints in the
        # representation of a problem, but the SOS
        # constraints can't have custom weights that
        # represent stochastic data
        for soscondata in self.reference_model.component_data_objects(
                SOSConstraint, active=True, descend_into=True):
            for vardata, weight in soscondata.get_items():
                weight_params = tuple(
                    self._collect_mutable_parameters(weight).values())
                if paramdata in self.stochastic_data:
                    raise ValueError(
                        "SOSConstraints with stochastic data are currently "
                        "not supported in implicit stochastic programs. The "
                        "SOS constraint component '%s' has a weight term for "
                        "variable '%s' that references stochastic parameter '%s'"
                        % (soscondata.name, vardata.name, paramdata.name))
                self.variable_to_constraints_map.\
                    setdefault(vardata, []).append(condata)
                self.constraint_to_variables_map.\
                    setdefault(condata, []).append(vardata)

        for vardata in self.reference_model.component_data_objects(
                Var, descend_into=True):

            lower_params = \
                tuple(self._collect_mutable_parameters(vardata.lb).values())
            self.variable_to_stochastic_data_lb_map[vardata] = []
            for paramdata in lower_params:
                if paramdata in self.stochastic_data:
                    self.stochastic_data_to_variables_lb_map.\
                        setdefault(paramdata, []).append(vardata)
                    self.variable_to_stochastic_data_lb_map[vardata].\
                        append(paramdata)
            if len(self.variable_to_stochastic_data_lb_map[vardata]) == 0:
                del self.variable_to_stochastic_data_lb_map[vardata]

            upper_params = \
                tuple(self._collect_mutable_parameters(vardata.ub).values())
            self.variable_to_stochastic_data_ub_map[vardata] = []
            for paramdata in upper_params:
                if paramdata in self.stochastic_data:
                    self.stochastic_data_to_variables_ub_map.\
                        setdefault(paramdata, []).append(vardata)
                    self.variable_to_stochastic_data_ub_map[vardata].\
                        append(paramdata)
            if len(self.variable_to_stochastic_data_ub_map[vardata]) == 0:
                del self.variable_to_stochastic_data_ub_map[vardata]
Beispiel #16
0
    def __init__(self, reference_model):

        self.reference_model = None
        self.objective = None
        self.time_stages = None

        self.stage_to_variables_map = {}
        self.variable_to_stage_map = {}

        # the set of stochastic data objects
        # (possibly mapped to some distribution)
        self.stochastic_data = None

        # maps between variables and objectives
        self.variable_to_objectives_map = ComponentMap()
        self.objective_to_variables_map = ComponentMap()

        # maps between variables and constraints
        self.variable_to_constraints_map = ComponentMap()
        self.constraint_to_variables_map = ComponentMap()

        # maps between stochastic data and objectives
        self.stochastic_data_to_objectives_map = ComponentMap()
        self.objective_to_stochastic_data_map = ComponentMap()

        # maps between stochastic data and constraints
        self.stochastic_data_to_constraints_map = ComponentMap()
        self.constraint_to_stochastic_data_map = ComponentMap()

        # maps between stochastic data and variable lower and upper bounds
        self.stochastic_data_to_variables_lb_map = ComponentMap()
        self.variable_to_stochastic_data_lb_map = ComponentMap()

        self.stochastic_data_to_variables_ub_map = ComponentMap()
        self.variable_to_stochastic_data_ub_map = ComponentMap()

        self.variable_symbols = ComponentMap()

        if not isinstance(reference_model, Block):
            raise TypeError("reference model input must be a Pyomo model")
        self.reference_model = reference_model

        #
        # Extract stochastic parameters from the
        # StochasticDataAnnotation object
        #
        self.stochastic_data = \
            _extract_stochastic_data(self.reference_model)

        #
        # Get the variable stages from the
        # VariableStageAnnotation object
        #
        (self.stage_to_variables_map,
         self.variable_to_stage_map,
         self._variable_stage_assignments) = \
            _map_variable_stages(self.reference_model)
        self.time_stages = tuple(sorted(self.stage_to_variables_map))
        assert self.time_stages[0] == 1
        self.variable_symbols = generate_cuid_names(self.reference_model,
                                                    ctype=Var)
        # remove the parent blocks from this map
        keys_to_delete = []
        for var in self.variable_symbols:
            if var.parent_component().type() is not Var:
                keys_to_delete.append(var)
        for key in keys_to_delete:
            del self.variable_symbols[key]

        #
        # Get the stage cost components from the StageCostAnnotation
        # and generate a dummy single-scenario scenario tree
        #
        stage_cost_annotation = locate_annotations(
            self.reference_model,
            StageCostAnnotation,
            max_allowed=1)
        if len(stage_cost_annotation) == 0:
            raise ValueError(
                "Reference model is missing stage cost "
                "annotation: %s" % (StageCostAnnotation.__name__))
        else:
            assert len(stage_cost_annotation) == 1
            stage_cost_annotation = stage_cost_annotation[0][1]
        stage_cost_assignments = ComponentMap(
            stage_cost_annotation.expand_entries())

        stage1_cost = None
        stage2_cost = None
        for cdata, stagenum in stage_cost_assignments.items():
            if stagenum == 1:
                stage1_cost = cdata
            elif stagenum == 2:
                stage2_cost = cdata
        if stage1_cost is None:
            raise ValueError("Missing stage cost annotation "
                             "for time stage: 1")
        if stage2_cost is None:
            raise ValueError("Missing stage cost annotation "
                             "for time stage: 2")
        assert stage1_cost is not stage2_cost
        self._stage1_cost = stage1_cost
        self._stage2_cost = stage2_cost

        #
        # Extract the locations of variables and stochastic data
        # within the model
        #
        sto_obj = StochasticObjectiveAnnotation()
        for objcntr, obj in enumerate(
                  self.reference_model.component_data_objects(
                Objective,
                active=True,
                descend_into=True), 1):

            if objcntr > 1:
                raise ValueError(
                    "Reference model can not contain more than one "
                    "active objective")

            self.objective = obj
            self.objective_sense = obj.sense

            obj_params = tuple(
                self._collect_mutable_parameters(obj.expr).values())
            self.objective_to_stochastic_data_map[obj] = []
            for paramdata in obj_params:
                if paramdata in self.stochastic_data:
                    self.stochastic_data_to_objectives_map.\
                        setdefault(paramdata, []).append(obj)
                    self.objective_to_stochastic_data_map[obj].\
                        append(paramdata)
            if len(self.objective_to_stochastic_data_map[obj]) == 0:
                del self.objective_to_stochastic_data_map[obj]
            else:
                # TODO: Can we make this declaration sparse
                #       by identifying which variables have
                #       stochastic coefficients? How to handle
                #       non-linear expressions?
                sto_obj.declare(obj)

            obj_variables = tuple(
                self._collect_variables(obj.expr).values())
            self.objective_to_variables_map[obj] = []
            for var in obj_variables:
                self.variable_to_objectives_map.\
                    setdefault(var, []).append(obj)
                self.objective_to_variables_map[obj].append(var)
            if len(self.objective_to_variables_map[obj]) == 0:
                del self.objective_to_variables_map[obj]

        sto_conbounds = StochasticConstraintBoundsAnnotation()
        sto_conbody = StochasticConstraintBodyAnnotation()
        for con in self.reference_model.component_data_objects(
                Constraint,
                active=True,
                descend_into=True):

            lower_params = tuple(
                self._collect_mutable_parameters(con.lower).values())
            body_params = tuple(
                self._collect_mutable_parameters(con.body).values())
            upper_params = tuple(
                self._collect_mutable_parameters(con.upper).values())

            # TODO: Can we make this declaration sparse
            #       by idenfifying which variables have
            #       stochastic coefficients? How to handle
            #       non-linear expressions? Currently, this
            #       code also fails to detect that mutable
            #       "constant" expressions might fall out
            #       of the body and into the bounds.
            if len(body_params):
                sto_conbody.declare(con)
            if len(body_params) or \
               len(lower_params) or \
               len(upper_params):
                sto_conbounds.declare(con,
                                      lb=bool(len(lower_params) or len(body_params)),
                                      ub=bool(len(upper_params) or len(body_params)))

            all_stochastic_params = {}
            for param in itertools.chain(lower_params,
                                         body_params,
                                         upper_params):
                if param in self.stochastic_data:
                    all_stochastic_params[id(param)] = param

            if len(all_stochastic_params) > 0:
                self.constraint_to_stochastic_data_map[con] = []
                # no params will appear twice in this iteration
                for param in all_stochastic_params.values():
                    self.stochastic_data_to_constraints_map.\
                        setdefault(param, []).append(con)
                    self.constraint_to_stochastic_data_map[con].\
                        append(param)

            body_variables = tuple(
                self._collect_variables(con.body).values())
            self.constraint_to_variables_map[con] = []
            for var in body_variables:
                self.variable_to_constraints_map.\
                    setdefault(var, []).append(con)
                self.constraint_to_variables_map[con].append(var)

        # For now, it is okay to have SOSConstraints in the
        # representation of a problem, but the SOS
        # constraints can't have custom weights that
        # represent stochastic data
        for soscon in self.reference_model.component_data_objects(
                SOSConstraint,
                active=True,
                descend_into=True):
            for var, weight in soscon.get_items():
                weight_params = tuple(
                    self._collect_mutable_parameters(weight).values())
                if param in self.stochastic_data:
                    raise ValueError(
                        "SOSConstraints with stochastic data are currently"
                        " not supported in embedded stochastic programs. "
                        "The SOSConstraint component '%s' has a weight "
                        "term for variable '%s' that references stochastic"
                        " parameter '%s'"
                        % (soscon.name,
                           var.name,
                           param.name))
                self.variable_to_constraints_map.\
                    setdefault(var, []).append(soscon)
                self.constraint_to_variables_map.\
                    setdefault(soscon, []).append(var)

        sto_varbounds = StochasticVariableBoundsAnnotation()
        for var in self.reference_model.component_data_objects(
                Var,
                descend_into=True):

            lower_params = tuple(
                self._collect_mutable_parameters(var.lb).values())
            upper_params = tuple(
                self._collect_mutable_parameters(var.ub).values())

            if (len(lower_params) > 0) or \
               (len(upper_params) > 0):
                sto_varbounds.declare(var,
                                      lb=bool(len(lower_params) > 0),
                                      ub=bool(len(upper_params) > 0))

            self.variable_to_stochastic_data_lb_map[var] = []
            for param in lower_params:
                if param in self.stochastic_data:
                    self.stochastic_data_to_variables_lb_map.\
                        setdefault(param, []).append(var)
                    self.variable_to_stochastic_data_lb_map[var].\
                        append(param)
            if len(self.variable_to_stochastic_data_lb_map[var]) == 0:
                del self.variable_to_stochastic_data_lb_map[var]

            self.variable_to_stochastic_data_ub_map[var] = []
            for param in upper_params:
                if param in self.stochastic_data:
                    self.stochastic_data_to_variables_ub_map.\
                        setdefault(param, []).append(var)
                    self.variable_to_stochastic_data_ub_map[var].\
                        append(param)
            if len(self.variable_to_stochastic_data_ub_map[var]) == 0:
                del self.variable_to_stochastic_data_ub_map[var]

        #
        # Generate the explicit annotations
        #

        # first make sure these annotations do not already exist
        if len(locate_annotations(self.reference_model,
                                  StochasticConstraintBoundsAnnotation)) > 0:
            raise ValueError("Reference model can not contain "
                             "a StochasticConstraintBoundsAnnotation declaration.")
        if len(locate_annotations(self.reference_model,
                                  StochasticConstraintBodyAnnotation)) > 0:
            raise ValueError("Reference model can not contain "
                             "a StochasticConstraintBodyAnnotation declaration.")
        if len(locate_annotations(self.reference_model,
                                  StochasticObjectiveAnnotation)) > 0:
            raise ValueError("Reference model can not contain "
                             "a StochasticObjectiveAnnotation declaration.")

        # now add any necessary annotations
        if sto_obj.has_declarations:
            assert not hasattr(self.reference_model,
                               ".pyspembeddedsp_stochastic_objective_annotation")
            setattr(self.reference_model,
                    ".pyspembeddedsp_stochastic_objective_annotation",
                    sto_obj)
        if sto_conbody.has_declarations:
            assert not hasattr(self.reference_model,
                               ".pyspembeddedsp_stochastic_constraint_body_annotation")
            setattr(self.reference_model,
                    ".pyspembeddedsp_stochastic_constraint_body_annotation",
                    sto_conbody)
        if sto_conbounds.has_declarations:
            assert not hasattr(self.reference_model,
                               ".pyspembeddedsp_stochastic_constraint_bounds_annotation")
            setattr(self.reference_model,
                    ".pyspembeddedsp_stochastic_constraint_bounds_annotation",
                    sto_conbounds)
        if sto_varbounds.has_declarations:
            assert not hasattr(self.reference_model,
                               ".pyspembeddedsp_stochastic_variable_bounds_annotation")
            setattr(self.reference_model,
                    ".pyspembeddedsp_stochastic_variable_bounds_annotation",
                    sto_varbounds)