Changeset 3038


Ignore:
Timestamp:
Sep 22, 2010 5:12:01 PM (11 years ago)
Author:
jwatson
Message:

Fairly extensive re-work and simplification of the PySP extensive form writer, focusing on a cleaner implementation of the cvar term generation.

Location:
coopr.pysp/trunk/coopr/pysp
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • coopr.pysp/trunk/coopr/pysp/ef.py

    r2993 r3038  
    6969#       instances are associated with the extensive form. this might be something we
    7070#       encapsulate at some later time.
     71# NOTE: if cvar terms are generated, then the input scenario tree is modified accordingly,
     72#       i.e., with the addition of the "eta" variable at the root node and the excess
     73#       variables at (for lack of a better place - they are per-scenario, but are not
     74#       blended) the second stage.
    7175#
    7276
     
    8993         print "CVaR alpha="+str(risk_alpha)
    9094         print ""
     95
     96      # update the scenario tree with cvar-specific variable names, so
     97      # they will get cloned accordingly in the master instance.
     98      first_stage = scenario_tree._stages[0]
     99      second_stage = scenario_tree._stages[1]
     100      root_node = first_stage._tree_nodes[0]
     101
     102      # NOTE: because we currently don't have access to the reference
     103      #       instance in this method, temporarily (and largely orphaned)
     104      #       variables are constructed to supply to the scenario tree.
     105      #       this decision should be ultimately revisited.
     106      cvar_eta_variable_name = "CVAR_ETA"
     107      cvar_eta_variable = Var(name=cvar_eta_variable_name)
     108      cvar_eta_variable.construct()               
     109
     110      first_stage.add_variable(cvar_eta_variable, "*", [None])
     111
     112      cvar_excess_variable_name = "CVAR_EXCESS"
     113      cvar_excess_variable = Var(name=cvar_excess_variable_name)
     114      cvar_excess_variable.construct()
     115
     116      second_stage.add_variable(cvar_excess_variable, "*", [None])
     117
     118      # create the eta and excess variable on a per-scenario basis,
     119      # in addition to the constraint relating to the two.
     120      for scenario_name, scenario_instance in scenario_instances.items():
     121
     122         cvar_excess_variable_name = "CVAR_EXCESS"
     123         cvar_excess_variable = Var(name=cvar_excess_variable_name, domain=NonNegativeReals)
     124         cvar_excess_variable.construct()         
     125         setattr(scenario_instance, cvar_excess_variable_name, cvar_excess_variable)
     126
     127         cvar_eta_variable_name = "CVAR_ETA"
     128         cvar_eta_variable = Var(name=cvar_eta_variable_name)
     129         cvar_eta_variable.construct()         
     130         setattr(scenario_instance, cvar_eta_variable_name, cvar_eta_variable)
     131
     132         compute_excess_constraint_name = "COMPUTE_SCENARIO_EXCESS"
     133         compute_excess_constraint = Constraint(name=compute_excess_constraint_name)
     134         compute_excess_expression = cvar_excess_variable
     135         for node in scenario_tree._scenario_map[scenario_name]._node_list:
     136            (cost_variable, cost_variable_idx) = node._stage._cost_variable
     137            compute_excess_expression -= getattr(scenario_instance, cost_variable.name)[cost_variable_idx]
     138         compute_excess_expression += cvar_eta_variable
     139         compute_excess_constraint.add(None, (0.0, compute_excess_expression, None))
     140         compute_excess_constraint._model = scenario_instance
     141         setattr(scenario_instance, compute_excess_constraint_name, compute_excess_constraint)
     142
     143         # re-process the scenario instance due to the newly added constraints/variables associated
     144         # with CVaR. a complete preprocess is overkill, of course - the correct approach would be
     145         # to just preprocess those newly added variables and constraints.
     146         scenario_instance.preprocess()
    91147
    92148   #
     
    118174            if stage != scenario_tree._stages[-1]:     
    119175
    120                master_variable_name = tree_node._name + "_" + stage_variable.name
     176               master_variable_name = stage_variable.name               
    121177
    122178               # because there may be a single stage variable and multiple indices, check
     
    156212                  if (is_used is True) and (is_fixed is False):
    157213                           
    158                      # the following is necessary, specifically to get the name - deepcopy won't reset these attributes.
    159                      # and because presolve/simplification is name-based, the names *have* to be different.
    160                      master_variable[index].var = master_variable
    161                      master_variable[index].name = tree_node._name + "_" + master_variable[index].name
    162 
    163214                     for scenario in tree_node._scenarios:
    164215                        scenario_instance = scenario_instances[scenario._name]
     
    181232         setattr(binding_instance, expected_cost_variable_name, expected_cost_variable)
    182233
    183    # if we're generating the weighted CVaR objective term, create the
    184    # corresponding variable and the master CVaR eta variable.
    185234   if generate_weighted_cvar is True:
    186235
    187       root_node = scenario_tree._stages[0]._tree_nodes[0]
    188      
    189236      cvar_cost_variable_name = "CVAR_COST_" + root_node._name
    190237      cvar_cost_variable = Var(name=cvar_cost_variable_name)
     238      cvar_cost_variable.construct()           
    191239      setattr(binding_instance, cvar_cost_variable_name, cvar_cost_variable)
    192       cvar_cost_variable.construct()
    193 
    194       cvar_eta_variable_name = "CVAR_ETA_" + root_node._name
    195       cvar_eta_variable = Var(name=cvar_eta_variable_name)
    196       setattr(binding_instance, cvar_eta_variable_name, cvar_eta_variable)     
    197       cvar_eta_variable.construct()
    198240
    199241   binding_instance.preprocess()
     
    245287   # i.e., the current node cost and the expected cost of the child nodes.
    246288   # if the root, then the constraint is just the objective.
    247 
    248289   for stage in scenario_tree._stages:
    249290
     
    284325               cvar_cost_variable = getattr(binding_instance, cvar_cost_variable_name)
    285326               if cvar_weight == 0.0:
     327                  # if the cvar weight is 0, then we're only doing cvar - no mean.
    286328                  opt_expression = cvar_cost_variable                             
    287329               else:
     
    298340   if generate_weighted_cvar is True:
    299341     
    300       root_node = scenario_tree._stages[0]._tree_nodes[0]
    301 
    302       master_cvar_eta_variable_name = "CVAR_ETA_" + root_node._name
    303       master_cvar_eta_variable = getattr(binding_instance, master_cvar_eta_variable_name)
    304      
    305       for scenario_name in scenario_instances.keys():
    306          scenario_instance = scenario_instances[scenario_name]
    307 
    308          # unique names are required because the presolve isn't
    309          # aware of the "owning" models for variables.
    310          cvar_excess_variable_name = "CVAR_EXCESS_"+scenario_name
    311          cvar_excess_variable = Var(name=cvar_excess_variable_name, domain=NonNegativeReals)
    312          setattr(scenario_instance, cvar_excess_variable_name, cvar_excess_variable)
    313          cvar_excess_variable.construct()
    314 
    315          cvar_eta_variable_name = "CVAR_ETA"
    316          cvar_eta_variable = Var(name=cvar_eta_variable_name)
    317          setattr(scenario_instance, cvar_eta_variable_name, cvar_eta_variable)
    318          cvar_eta_variable.construct()
    319 
    320          compute_excess_constraint_name = "COMPUTE_SCENARIO_EXCESS"
    321          compute_excess_constraint = Constraint(name=compute_excess_constraint_name)
    322          compute_excess_expression = cvar_excess_variable
    323          for node in scenario_tree._scenario_map[scenario_name]._node_list:
    324             (cost_variable, cost_variable_idx) = node._stage._cost_variable
    325             compute_excess_expression -= getattr(scenario_instance, cost_variable.name)[cost_variable_idx]
    326          compute_excess_expression += cvar_eta_variable
    327          compute_excess_constraint.add(None, (0.0, compute_excess_expression, None))
    328          compute_excess_constraint._model = scenario_instance
    329          setattr(scenario_instance, compute_excess_constraint_name, compute_excess_constraint)
    330 
    331          eta_equality_constraint_name = "MASTER_ETA_EQUALITY_WITH_" + scenario_instance.name
    332          eta_equality_constraint = Constraint(name=eta_equality_constraint_name)
    333          eta_equality_expr = master_cvar_eta_variable - cvar_eta_variable
    334          eta_equality_constraint.add(None, (0.0, eta_equality_expr, 0.0))
    335          eta_equality_constraint._model = binding_instance
    336          setattr(binding_instance, eta_equality_constraint_name, eta_equality_constraint)
    337 
    338          # re-process the scenario instance due to the newly added constraints/variables associated
    339          # with CVaR. a complete preprocess is overkill, of course - the correct approach would be
    340          # to just preprocess those newly added variables and constraints.
    341          scenario_instance.preprocess()
    342 
    343342      # add the constraint to compute the master CVaR variable value. iterate
    344343      # over scenario instances to create the expected excess component first.
    345344      cvar_cost_variable_name = "CVAR_COST_" + root_node._name
    346345      cvar_cost_variable = getattr(binding_instance, cvar_cost_variable_name)
    347       cvar_eta_variable_name = "CVAR_ETA_" + root_node._name
     346      cvar_eta_variable_name = "CVAR_ETA"
    348347      cvar_eta_variable = getattr(binding_instance, cvar_eta_variable_name)
    349348     
    350349      cvar_cost_expression = cvar_cost_variable - cvar_eta_variable
    351350     
    352       for scenario_name in scenario_instances.keys():
    353          scenario_instance = scenario_instances[scenario_name]
     351      for scenario_name, scenario_instance in scenario_instances.items():
     352
    354353         scenario_probability = scenario_tree._scenario_map[scenario_name]._probability
    355354
    356          scenario_excess_variable_name = "CVAR_EXCESS_"+scenario_name
     355         scenario_excess_variable_name = "CVAR_EXCESS"
    357356         scenario_excess_variable = getattr(scenario_instance, scenario_excess_variable_name)
    358357
  • coopr.pysp/trunk/coopr/pysp/scenariotree.py

    r2852 r3038  
    2323
    2424   #
    25    # initialize the _solutions attribute of a tree node.
    26    #
    27 
    28    def _initialize_solutions(self):
     25   # update the _solutions attribute of a tree node, given a specific
     26   # variable/match-template/variable-index triple.
     27   #
     28   def _update_solution_map(self, variable, match_template, variable_indices):
     29
     30      # don't bother copying bounds for variables, as the values stored
     31      # here are computed elsewhere - and that entity is responsible for
     32      # ensuring feasibility. this also leaves room for specifying infeasible
     33      # or partial solutions.
     34      new_variable_index = variable._index
     35      new_variable_name = variable.name
     36      new_variable = None
     37      if (len(new_variable_index) is 1) and (None in new_variable_index):
     38         new_variable = Var(name=new_variable_name)
     39      else:
     40         new_variable = Var(new_variable_index, name=new_variable_name)
     41      new_variable.construct()
     42
     43      # by default, deactive all variable values - we're copying the
     44      # full index set of a variable, which is necessarily wasteful and
     45      # incorrect. then, do a second pass and activate those indicies
     46      # that are actually associated with this tree node.
     47      for index in new_variable_index:
     48         new_variable[index].deactivate()
     49
     50      for index in variable_indices:
     51         new_variable[index].activate()
     52
     53      self._solutions[new_variable_name] = new_variable     
     54
     55   #
     56   # initialize the _solutions attribute of a tree node, from scratch.
     57   #
     58
     59   def _initialize_solution_map(self):
     60
     61      # clear whatever was there before.
     62      self._solutions = {}
    2963
    3064      # NOTE: Given the expense, this should really be optional - don't
     
    3670      # is an issue (space is the most likely issue)
    3771      for variable, match_template, variable_indices in self._stage._variables:
    38 
    39          # don't bother copying bounds for variables, as the values stored
    40          # here are computed elsewhere - and that entity is responsible for
    41          # ensuring feasibility. this also leaves room for specifying infeasible
    42          # or partial solutions.
    43          new_variable_index = variable._index
    44          new_variable_name = variable.name
    45          new_variable = None
    46          if (len(new_variable_index) is 1) and (None in new_variable_index):
    47             new_variable = Var(name=new_variable_name)
    48          else:
    49             new_variable = Var(new_variable_index, name=new_variable_name)
    50          new_variable.construct()
    51 
    52          # by default, deactive all variable values - we're copying the
    53          # full index set of a variable, which is necessarily wasteful and
    54          # incorrect. then, do a second pass and activate those indicies
    55          # that are actually associated with this tree node.
    56          for index in new_variable_index:
    57             new_variable[index].deactivate()
    58 
    59          for index in variable_indices:
    60             new_variable[index].activate()
    61 
    62          self._solutions[new_variable_name] = new_variable
    63 
    64       self._solutions_initialized = True
     72         self._update_solution_map(variable, match_template, variable_indices)
     73
     74      self._solution_map_initialized = True
    6575
    6676
     
    92102
    93103      # a flag indicating whether the _solutions attribute has been properly initialized.
    94       self._solutions_initialized = False
     104      self._solution_map_initialized = False
    95105
    96106      # solution (variable) values for this node. assumed to be distinct
     
    101111
    102112      if initialize_solution is True:
    103          self._initialize_solutions()
     113         self._initialize_solution_map()
    104114
    105115   #
     
    110120   def snapshotSolutionFromAverages(self):
    111121
    112       if self._solutions_initialized is False:
    113          self._initialize_solutions()
     122      if self._solution_map_initialized is False:
     123         self._initialize_solution_map()
    114124
    115125      for variable_name, variable in self._solutions.items():
     
    134144   def snapshotSolutionFromInstances(self, scenario_instance_map):
    135145
    136       if self._solutions_initialized is False:
    137          self._initialize_solutions()
     146      if self._solution_map_initialized is False:
     147         self._initialize_solution_map()
    138148
    139149      for variable_name, variable in self._solutions.items():
     
    174184   """
    175185   def __init__(self, *args, **kwds):
     186
    176187      self._name = ""
    177       self._tree_nodes = []      # a collection of ScenarioTreeNodes
    178       # a collection of pairs consisting of (1) references to pyomo model Vars, (2) the original match template string (for output purposes),
    179       # and (3) a *list* of the corresponding indices.
    180       # the variables are references to those objects belonging to the instance in the parent ScenarioTree.
     188     
     189      # a collection of ScenarioTreeNode objects.
     190      self._tree_nodes = []     
     191     
     192      # a collection of triples consisting of (1) a reference to a Pyomo model Var object, (2) the original match
     193      # template string (for output purposes), and (3) a *list* of the corresponding indices. the variables are
     194      # references to those objects belonging to the Pyomo reference scenario instance associated with the parent
     195      # ScenarioTree of this Stage.
    181196      # NOTE: if the variable index is none, it is assumed that the entire variable is blended.
    182197      self._variables = []
    183       # a tuple consisting of (1) a reference to a pyomo model Var that computes the stage-specific cost and (2) the corresponding index.
    184       # the index *is* the sole index in the cost variable, as the cost variable refers to a single variable index.
     198     
     199      # a tuple consisting of (1) a reference to a pyomo model Var that computes the stage-specific cost and (2) the corresponding
     200      # index. the index *is* the sole index in the cost variable, as the cost variable refers to a single variable index.
    185201      self._cost_variable = (None, None)
     202
     203   #
     204   # add a new variable to the stage, which will include updating the solution maps for each associated ScenarioTreeNode.
     205   #
     206   def add_variable(self, variable, match_template, indices):
     207
     208      self._variables.append((variable, match_template, indices))
     209
     210      for tree_node in self._tree_nodes:
     211         tree_node._update_solution_map(variable, match_template, indices)
    186212
    187213class Scenario(object):
     
    190216   """
    191217   def __init__(self, *args, **kwds):
     218     
    192219      self._name = None
    193220      self._leaf_node = None  # allows for construction of node list
     
    328355   """
    329356   def __init__(self, *args, **kwds):
    330       self._name = None # TBD - some arbitrary identifier
    331       self._reference_instance = None # TBD - the reference (deterministic) base model
     357     
     358      self._name = None # some arbitrary identifier
     359      self._reference_instance = None # the reference (deterministic) base model
    332360
    333361      # the core objects defining the scenario tree.
     
    341369      self._scenario_map = {}
    342370
    343       # mapping of stages to sets of variables which belong in the corresponding stage.
    344       self._stage_variables = {}
    345 
    346371      # a boolean indicating how data for scenario instances is specified.
    347372      # possibly belongs elsewhere, e.g., in the PH algorithm.
    348373      self._scenario_based_data = None
    349 
    350       # every stage has a cost variable - this is a variable/index pair.
    351       self._cost_variable = None
    352374
    353375      scenario_tree_instance = None
Note: See TracChangeset for help on using the changeset viewer.