source: coopr.pysp/trunk/coopr/pysp/scenariotree.py @ 2450

Last change on this file since 2450 was 2450, checked in by jwatson, 11 years ago

Added code to the PySP scenario tree class to facilitate computation (snapshotting) of tree node variables directly from the composite instances.

File size: 35.1 KB
Line 
1#  _________________________________________________________________________
2#
3#  Coopr: A COmmon Optimization Python Repository
4#  Copyright (c) 2008 Sandia Corporation.
5#  This software is distributed under the BSD License.
6#  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
7#  the U.S. Government retains certain rights in this software.
8#  For more information, see the Coopr README.txt file.
9#  _________________________________________________________________________
10
11import sys
12import types
13import copy
14import os.path
15import traceback
16
17from coopr.pyomo import *
18from phutils import *
19
20class ScenarioTreeNode(object):
21
22   """ Constructor
23   """
24   def __init__(self, name, conditional_probability, stage, reference_instance):
25
26      self._name = name
27      self._stage = stage
28      self._parent = None
29      self._children = [] # a collection of ScenarioTreeNodes
30      self._conditional_probability = conditional_probability # conditional on parent
31      self._scenarios = [] # a collection of all Scenarios passing through this node in the tree
32
33      # general use statistics for the variables at each node.
34      # each attribute is a map between the variable name and a
35      # parameter (over the same index set) encoding the corresponding
36      # statistic computed over all scenarios for that node. the
37      # parameters are named as the source variable name suffixed
38      # by one of: "NODEMIN", "NODEAVG", and "NODEMAX".
39      # NOTE: the averages are probability_weighted - the min/max
40      #       values are not.
41      # NOTE: the parameter names are basically irrelevant, and the
42      #       convention is assumed to be enforced by whoever populates
43      #       these parameters.
44      self._averages = {} 
45      self._minimums = {}
46      self._maximums = {}
47
48      # solution (variable) values for this node. assumed to be distinct
49      # from self._averages, as the latter are not necessarily feasible.
50      # objects in the map are actual pyomo Var instances; keys are
51      # variable names.
52      self._solutions = {}
53
54      # NOTE: Given the expense, this should really be optional - don't
55      #       construct unless we're actually going to use!
56      # for each variable referenced in the stage, clone the variable
57      # for purposes of storing solutions. we are being wasteful in
58      # terms copying indices that may not be referenced in the stage.
59      # this is something that we might revisit if space/performance
60      # is an issue (space is the most likely issue)
61      for variable, match_template, variable_indices in self._stage._variables:
62
63         # don't bother copying bounds for variables, as the values stored
64         # here are computed elsewhere - and that entity is responsible for
65         # ensuring feasibility. this also leaves room for specifying infeasible
66         # or partial solutions.       
67         new_variable_index = variable._index
68         new_variable_name = variable.name
69         new_variable = None
70         if (len(new_variable_index) is 1) and (None in new_variable_index):
71            new_variable = Var(name=new_variable_name)
72         else:
73            new_variable = Var(new_variable_index, name=new_variable_name)
74         new_variable.construct()
75
76         # by default, deactive all variable values - we're copying the
77         # full index set of a variable, which is necessarily wasteful and
78         # incorrect. then, do a second pass and activate those indicies
79         # that are actually associated with this tree node.
80         for index in new_variable_index:
81            new_variable[index].deactivate()
82
83         for index in variable_indices:
84            new_variable[index].activate()
85
86         self._solutions[new_variable_name] = new_variable
87
88   #
89   # copies the parameter values values from the _averages attribute
90   # into the _solutions attribute - only for active variable values.
91   #
92
93   def snapshotSolutionFromAverages(self):
94
95      for variable_name, variable in self._solutions.items():
96
97         # try and grab the corresponding averages parameter - if it
98         # doesn't exist, throw an exception.
99         average_parameter = None
100         try:
101            average_parameter = self._averages[variable_name]
102         except:
103            raise RuntimeError, "No averages parameter present on tree node="+self._name+" for variable="+variable_name
104           
105         for index in variable._index:
106            if variable[index].active is True:
107               variable[index] = average_parameter[index]()
108
109   #
110   # computes the solution values from the composite scenario instances at this tree node.
111   # the input scenario_instance_map is a map from scenario name to instance objects.
112   #
113
114   def snapshotSolutionFromInstances(self, scenario_instance_map):
115
116      for variable_name, variable in self._solutions.items():
117         for index in variable._index:
118            if variable[index].active is True:
119               node_probability = 0.0
120               avg = 0.0
121               for scenario in self._scenarios:
122                  scenario_instance = scenario_instance_map[scenario._name]
123                  node_probability += scenario._probability
124                  var_value = getattr(scenario_instance, variable.name)[index].value
125                  avg += (scenario._probability * var_value)
126               variable[index].value = avg / node_probability
127           
128   #
129   # a utility to compute the cost of the current node plus the expected costs of child nodes.
130   #
131
132   def computeExpectedNodeCost(self, scenario_instance_map):
133
134      # IMPT: This implicitly assumes convergence across the scenarios - if not, garbage results.
135      instance = scenario_instance_map[self._scenarios[0]._name]
136      my_cost = instance.active_components(Var)[self._stage._cost_variable[0].name][self._stage._cost_variable[1]]()
137      child_cost = 0.0
138      for child in self._children:
139         child_cost += (child._conditional_probability * child.computeExpectedNodeCost(scenario_instance_map))
140      return my_cost + child_cost
141
142
143class Stage(object):
144
145   """ Constructor
146   """
147   def __init__(self, *args, **kwds):
148      self._name = ""
149      self._tree_nodes = []      # a collection of ScenarioTreeNodes
150      # a collection of pairs consisting of (1) references to pyomo model Vars, (2) the original match template string (for output purposes),
151      # and (3) a *list* of the corresponding indices.
152      # the variables are references to those objects belonging to the instance in the parent ScenarioTree.
153      # NOTE: if the variable index is none, it is assumed that the entire variable is blended.
154      self._variables = []
155      # a tuple consisting of (1) a reference to a pyomo model Var that computes the stage-specific cost and (2) the corresponding index.
156      # the index *is* the sole index in the cost variable, as the cost variable refers to a single variable index.
157      self._cost_variable = (None, None)
158
159class Scenario(object):
160
161   """ Constructor
162   """
163   def __init__(self, *args, **kwds):
164      self._name = None
165      self._leaf_node = None  # allows for construction of node list
166      self._node_list = []    # sequence from parent to leaf of ScenarioTreeNodes
167      self._probability = 0.0 # the unconditional probability for this scenario, computed from the node list
168
169class ScenarioTree(object):
170
171   # a utility to construct the stage objects for this scenario tree.
172   # operates strictly by side effects, initializing the self
173   # _stages and _stage_map attributes.
174   def _construct_stages(self, stage_ids, stage_variable_ids, stage_cost_variable_ids):
175
176      # construct the stage objects, which will leave them
177      # largely uninitialized - no variable information, in particular.
178      for stage_name in stage_ids:
179         new_stage = Stage()
180         new_stage._name = stage_name
181         self._stages.append(new_stage)
182         self._stage_map[stage_name] = new_stage
183
184      # initialize the variable collections (blended and cost) for each stage.
185      # these are references, not copies.
186      for stage_id in stage_variable_ids.keys():
187
188         if stage_id not in self._stage_map.keys():
189            raise ValueError, "Unknown stage=" + stage_id + " specified in scenario tree constructor (stage->variable map)"
190
191         stage = self._stage_map[stage_id]
192         variable_ids = stage_variable_ids[stage_id]
193
194         for variable_string in variable_ids:
195
196            if isVariableNameIndexed(variable_string) is True:
197
198               variable_name, index_template = extractVariableNameAndIndex(variable_string)
199
200               # validate that the variable exists and extract the reference.
201               if variable_name not in self._reference_instance.active_components(Var):
202                  raise ValueError, "Variable=" + variable_name + " associated with stage=" + stage_id + " is not present in model=" + self._reference_instance.name
203               variable = self._reference_instance.active_components(Var)[variable_name]               
204
205               # extract all "real", i.e., fully specified, indices matching the index template.
206               match_indices = extractVariableIndices(variable, index_template)               
207
208               # there is a possibility that no indices match the input template.
209               # if so, let the user know about it.
210               if len(match_indices) == 0:
211                  raise RuntimeError, "No indices match template="+str(index_template)+" for variable="+variable_name+" ; encountered in scenario tree specification for model="+self._reference_instance.name
212                 
213               stage._variables.append((variable, index_template, match_indices))
214
215            else:
216
217               # verify that the variable exists.
218               if variable_string not in self._reference_instance.active_components(Var).keys():
219                  raise RuntimeError, "Unknown variable=" + variable_string + " associated with stage=" + stage_id + " is not present in model=" + self._reference_instance.name
220
221               variable = self._reference_instance.active_components(Var)[variable_string]
222
223               # 9/14/2009 - now forcing the user to explicit specify the full
224               # match template (e.g., "foo[*,*]") instead of just the variable
225               # name (e.g., "foo") to represent the set of all indices.
226               
227               # if the variable is a singleton - that is, non-indexed - no brackets is fine.
228               # we'll just tag the var[None] variable value with the (suffix,value) pair.
229               if None not in variable._index:
230                  raise RuntimeError, "Variable="+variable_string+" is an indexed variable, and templates must specify an index match; encountered in scenario tree specification for model="+self._reference_instance.name                 
231
232               match_indices = []
233               match_indices.append(None)
234
235               stage._variables.append((variable, "", match_indices))
236
237      for stage_id in stage_cost_variable_ids.keys():
238
239         if stage_id not in self._stage_map.keys():
240            raise ValueError, "Unknown stage=" + stage_id + " specified in scenario tree constructor (stage->cost variable map)"
241         stage = self._stage_map[stage_id]
242         
243         cost_variable_string = stage_cost_variable_ids[stage_id].value # de-reference is required to access the parameter value
244
245         # to be extracted from the string.
246         cost_variable_name = None
247         cost_variable = None
248         cost_variable_index = None
249
250         # do the extraction.
251         if isVariableNameIndexed(cost_variable_string) is True:
252
253            cost_variable_name, index_template = extractVariableNameAndIndex(cost_variable_string)
254
255            # validate that the variable exists and extract the reference.
256            if cost_variable_name not in self._reference_instance.active_components(Var):
257               raise ValueError, "Variable=" + cost_variable_name + " associated with stage=" + stage_id + " is not present in model=" + self._reference_instance.name
258            cost_variable = self._reference_instance.active_components(Var)[cost_variable_name]               
259
260            # extract all "real", i.e., fully specified, indices matching the index template.
261            match_indices = extractVariableIndices(cost_variable, index_template)
262
263            # only one index can be supplied for a stage cost variable.
264            if len(match_indices) != 1:
265               raise RuntimeError, "Only one index can be specified for a stage cost variable - "+str(len(match_indices))+"match template="+index_template+" for variable="+cost_variable_name+" ; encountered in scenario tree specification for model="+self._reference_instance.name
266
267            cost_variable_index = match_indices[0]
268
269         else:
270
271            cost_variable_name = cost_variable_string
272
273            # validate that the variable exists and extract the reference
274            if cost_variable_name not in self._reference_instance.active_components(Var):
275               raise ValueError, "Cost variable=" + cost_variable_name + " associated with stage=" + stage_id + " is not present in model=" + self._reference_instance.name
276            cost_variable = self._reference_instance.active_components(Var)[cost_variable_name]
277           
278         # store the validated info.
279         stage._cost_variable = (cost_variable, cost_variable_index)
280
281   """ Constructor
282       Arguments:
283           scenarioinstance     - the reference (deterministic) scenario instance.
284           scenariotreeinstance - the pyomo model specifying all scenario tree (text) data.
285           scenariobundlelist   - a list of scenario names to retain, i.e., cull the rest to create a reduced tree!
286   """ 
287   def __init__(self, *args, **kwds):
288      self._name = None # TBD - some arbitrary identifier
289      self._reference_instance = None # TBD - the reference (deterministic) base model
290
291      # the core objects defining the scenario tree.
292      self._tree_nodes = [] # collection of ScenarioTreeNodes
293      self._stages = [] # collection of Stages - assumed to be in time-order. the set (provided by the user) itself *must* be ordered.
294      self._scenarios = [] # collection of Scenarios
295
296      # dictionaries for the above.
297      self._tree_node_map = {}
298      self._stage_map = {}
299      self._scenario_map = {}
300
301      # mapping of stages to sets of variables which belong in the corresponding stage.
302      self._stage_variables = {}
303
304      # a boolean indicating how data for scenario instances is specified.
305      # possibly belongs elsewhere, e.g., in the PH algorithm.
306      self._scenario_based_data = None
307
308      # every stage has a cost variable - this is a variable/index pair.
309      self._cost_variable = None
310
311      scenario_tree_instance = None
312      scenario_bundle_list = None
313
314      # process the keyword options
315      for key in kwds.keys():
316         if key == "scenarioinstance":
317            self._reference_instance = kwds[key]
318         elif key == "scenariotreeinstance":
319            scenario_tree_instance = kwds[key]           
320         elif key == "scenariobundlelist":
321            scenario_bundle_list = kwds[key]           
322         else:
323            print "Unknown option=" + key + " specified in call to ScenarioTree constructor"
324
325      if self._reference_instance is None:
326         raise ValueError, "A reference scenario instance must be supplied in the ScenarioTree constructor"
327
328      if scenario_tree_instance is None:
329         raise ValueError, "A scenario tree instance must be supplied in the ScenarioTree constructor"
330
331      node_ids = scenario_tree_instance.Nodes
332      node_child_ids = scenario_tree_instance.Children
333      node_stage_ids = scenario_tree_instance.NodeStage
334      node_probability_map = scenario_tree_instance.ConditionalProbability
335      stage_ids = scenario_tree_instance.Stages
336      stage_variable_ids = scenario_tree_instance.StageVariables
337      stage_cost_variable_ids = scenario_tree_instance.StageCostVariable
338      scenario_ids = scenario_tree_instance.Scenarios
339      scenario_leaf_ids = scenario_tree_instance.ScenarioLeafNode
340      scenario_based_data = scenario_tree_instance.ScenarioBasedData
341
342      # save the method for instance data storage.
343      self._scenario_based_data = scenario_based_data()
344
345      # the input stages must be ordered, for both output purposes and knowledge of the final stage.
346      if stage_ids.ordered is False:
347         raise ValueError, "An ordered set of stage IDs must be supplied in the ScenarioTree constructor"
348
349      #     
350      # construct the actual tree objects
351      #
352
353      # construct the stage objects w/o any linkages first; link them up
354      # with tree nodes after these have been fully constructed.
355      self._construct_stages(stage_ids, stage_variable_ids, stage_cost_variable_ids)
356
357      # construct the tree node objects themselves in a first pass,
358      # and then link them up in a second pass to form the tree.
359      # can't do a single pass because the objects may not exist.
360      for tree_node_name in node_ids:
361
362         if tree_node_name not in node_stage_ids:
363            raise ValueError, "No stage is assigned to tree node=" + tree_node._name
364
365         stage_name = node_stage_ids[tree_node_name].value
366         if stage_name not in self._stage_map.keys():
367            raise ValueError, "Unknown stage=" + stage_name + " assigned to tree node=" + tree_node._name
368
369         new_tree_node = ScenarioTreeNode(tree_node_name, 
370                                          node_probability_map[tree_node_name].value, 
371                                          self._stage_map[stage_name],
372                                          self._reference_instance)
373
374         self._tree_nodes.append(new_tree_node)
375         self._tree_node_map[tree_node_name] = new_tree_node
376         self._stage_map[stage_name]._tree_nodes.append(new_tree_node)
377
378      # link up the tree nodes objects based on the child id sets.
379      for this_node in self._tree_nodes:
380         this_node._children = []
381         if this_node._name in node_child_ids: # otherwise, you're at a leaf and all is well.
382            child_ids = node_child_ids[this_node._name]
383            for child_id in child_ids:
384               if child_id in self._tree_node_map.keys():
385                  child_node = self._tree_node_map[child_id]
386                  this_node._children.append(self._tree_node_map[child_id])
387                  if child_node._parent is None:
388                     child_node._parent = this_node
389                  else:
390                     raise ValueError, "Multiple parents specified for tree node="+child_id+"; existing parent node="+child_node._parent._name+"; conflicting parent node="+this_node._name
391               else:
392                  raise ValueError, "Unknown child tree node=" + child_id + " specified for tree node=" + this_node._name
393
394      # at this point, the scenario tree nodes and the stages are set - no
395      # two-pass logic necessary when constructing scenarios.
396      for scenario_name in scenario_ids:
397         new_scenario = Scenario()
398         new_scenario._name=scenario_name
399
400         if scenario_name not in scenario_leaf_ids.keys():
401            raise ValueError, "No leaf tree node specified for scenario=" + scenario_name
402         else:
403            scenario_leaf_node_name = scenario_leaf_ids[scenario_name].value
404            if scenario_leaf_node_name not in self._tree_node_map.keys():
405               raise ValueError, "Uknown tree node=" + scenario_leaf_node_name + " specified as leaf of scenario=" + scenario_name
406            else:
407               new_scenario._leaf_node = self._tree_node_map[scenario_leaf_node_name]
408
409         current_node = new_scenario._leaf_node
410         probability = 1.0
411         while current_node is not None:
412            new_scenario._node_list.append(current_node)
413            current_node._scenarios.append(new_scenario) # links the scenarios to the nodes to enforce necessary non-anticipativity
414            probability *= current_node._conditional_probability
415            current_node = current_node._parent
416         new_scenario._node_list.reverse()
417         new_scenario._probability = probability
418
419         self._scenarios.append(new_scenario)
420         self._scenario_map[scenario_name] = new_scenario
421
422      # for output purposes, it is useful to known the maximal length of identifiers
423      # in the scenario tree for any particular category. I'm building these up
424      # incrementally, as they are needed. 0 indicates unassigned.
425      self._max_scenario_id_length = 0
426
427      # does the actual traversal to populate the members.
428      self.computeIdentifierMaxLengths()
429
430      # if a sub-bundle of scenarios has been specified, mark the
431      # active scenario tree components and compress the tree.
432      if scenario_bundle_list is not None:
433         print "Compressing scenario tree!"
434         self.compress(scenario_bundle_list)
435
436   #
437   # is the indicated scenario in the tree?
438   #
439
440   def contains_scenario(self, name):
441      return name in self._scenario_map.keys()
442
443   #
444   # get the scenario object from the tree.
445   #
446
447   def get_scenario(self, name):
448      return self._scenario_map[name]
449
450   #
451   # compute the scenario cost for the input instance, i.e.,
452   # the sum of all stage cost variables.
453   #
454
455   def compute_scenario_cost(self, instance):
456      aggregate_cost = 0.0
457      for stage in self._stages:
458         instance_cost_variable = instance.active_components(Var)[stage._cost_variable[0].name][stage._cost_variable[1]]()
459         aggregate_cost += instance_cost_variable
460      return aggregate_cost
461
462   #
463   # utility for compressing or culling a scenario tree based on
464   # a provided list of scenarios (specified by name) to retain -
465   # all non-referenced components are eliminated. this particular
466   # method compresses *in-place*, i.e., via direct modification
467   # of the scenario tree structure.
468   #
469
470   def compress(self, scenario_bundle_list):
471
472      # scan for and mark all referenced scenarios and
473      # tree nodes in the bundle list - all stages will
474      # obviously remain.
475      for scenario_name in scenario_bundle_list:
476         if scenario_name not in self._scenario_map:
477            raise ValueError, "Scenario="+scenario_name+" selected for bundling not present in scenario tree"
478         scenario = self._scenario_map[scenario_name]
479         scenario.retain = True
480
481         # chase all nodes comprising this scenario,
482         # marking them for retention.
483         for node in scenario._node_list:
484            node.retain = True
485
486      # scan for any non-retained scenarios and tree nodes.
487      scenarios_to_delete = []
488      tree_nodes_to_delete = []
489      for scenario in self._scenarios:
490         if hasattr(scenario, "retain") is True:
491            delattr(scenario, "retain")
492            pass
493         else:
494            scenarios_to_delete.append(scenario)             
495            del self._scenario_map[scenario._name]
496
497      for tree_node in self._tree_nodes:
498         if hasattr(tree_node, "retain") is True:
499            delattr(tree_node, "retain")
500            pass
501         else:
502            tree_nodes_to_delete.append(tree_node)
503            del self._tree_node_map[tree_node._name]
504
505      # JPW does not claim the following routines are
506      # the most efficient. rather, they get the job
507      # done while avoiding serious issues with
508      # attempting to remove elements from a list that
509      # you are iterating over.
510
511      # delete all references to unmarked scenarios
512      # and child tree nodes in the scenario tree node
513      # structures.
514      for tree_node in self._tree_nodes:
515         for scenario in scenarios_to_delete:
516            if scenario in tree_node._scenarios:
517               tree_node._scenarios.remove(scenario)
518         for node_to_delete in tree_nodes_to_delete:
519            if node_to_delete in tree_node._children:
520               tree_node._children.remove(node_to_delete)
521
522      # delete all references to unmarked tree nodes
523      # in the scenario tree stage structures.
524      for stage in self._stages:
525         for tree_node in tree_nodes_to_delete:
526            if tree_node in stage._tree_nodes:
527               stage._tree_nodes.remove(tree_node)
528
529      # delete all unreferenced entries from the core scenario
530      # tree data structures.
531      for scenario in scenarios_to_delete:
532         self._scenarios.remove(scenario)
533      for tree_node in tree_nodes_to_delete:
534         self._tree_nodes.remove(tree_node)
535
536
537      # re-normalize the conditional probabilities of the
538      # children at each tree node.
539      for tree_node in self._tree_nodes:
540         sum_child_probabilities = 0.0
541         for child_node in tree_node._children:
542            sum_child_probabilities += child_node._conditional_probability
543         for child_node in tree_node._children:
544            child_node._conditional_probability = child_node._conditional_probability / sum_child_probabilities
545
546      # re-compute the absolute scenario probabilities based
547      # on the re-normalized conditional node probabilities.
548      for scenario in self._scenarios:
549         probability = 1.0
550         for tree_node in scenario._node_list:
551            probability = probability * tree_node._conditional_probability
552         scenario._probability = probability
553
554   #
555   # returns the root node of the scenario tree
556   #
557
558   def findRootNode(self):
559
560      for tree_node in self._tree_nodes:
561         if tree_node._parent is None:
562            return tree_node
563      return None
564
565   #
566   # a utility function to compute, based on the current scenario tree content,
567   # the maximal length of identifiers in various categories.
568   #
569
570   def computeIdentifierMaxLengths(self):
571
572      self._max_scenario_id_length = 0
573      for scenario in self._scenarios:
574         if len(scenario._name) > self._max_scenario_id_length:
575            self._max_scenario_id_length = len(scenario._name)
576
577   #
578   # a utility function to (partially, at the moment) validate a scenario tree
579   #
580
581   def validate(self):
582
583      # for any node, the sum of conditional probabilities of the children should sum to 1.
584      for tree_node in self._tree_nodes:
585         sum_probabilities = 0.0
586         if len(tree_node._children) > 0:
587            for child in tree_node._children:
588               sum_probabilities += child._conditional_probability
589            if abs(1.0 - sum_probabilities) > 0.000001:
590               print "The child conditional probabilities for tree node=" + tree_node._name + " sum to " + `sum_probabilities`
591               return False
592
593      # ensure that there is only one root node in the tree
594      num_roots = 0
595      root_ids = []
596      for tree_node in self._tree_nodes:
597         if tree_node._parent is None:
598            num_roots += 1
599            root_ids.append(tree_node._name)
600
601      if num_roots != 1:
602         print "Illegal set of root nodes detected: " + `root_ids`
603         return False
604
605      # there must be at least one scenario passing through each tree node.
606      for tree_node in self._tree_nodes:
607         if len(tree_node._scenarios) == 0:
608            print "There are no scenarios associated with tree node=" + tree_node._name
609            return False
610     
611      return True
612
613   #
614   # copies the parameter values stored in any tree node _averages attribute
615   # into any tree node _solutions attribute - only for active variable values.
616   #
617
618   def snapshotSolutionFromAverages(self):
619
620      for tree_node in self._tree_nodes:
621
622         tree_node.snapshotSolutionFromAverages()
623
624   #
625   # computes the variable values at each tree node from the input scenario instances.
626   #
627
628   def snapshotSolutionFromInstances(self, scenario_instance_map):
629
630      for tree_node in self._tree_nodes:
631
632         tree_node.snapshotSolutionFromInstances(scenario_instance_map)         
633
634   #
635   # a utility function to pretty-print the static/non-cost information associated with a scenario tree
636   #
637
638   def pprint(self):
639
640      print "Scenario Tree Detail"
641
642      print "----------------------------------------------------"
643      if self._reference_instance is not None:
644         print "Model=" + self._reference_instance.name
645      else:
646         print "Model=" + "Unassigned"
647      print "----------------------------------------------------"         
648      print "Tree Nodes:"
649      print ""
650      for tree_node_name in sorted(self._tree_node_map.keys()):
651         tree_node = self._tree_node_map[tree_node_name]
652         print "\tName=" + tree_node_name
653         if tree_node._stage is not None:
654            print "\tStage=" + tree_node._stage._name
655         else:
656            print "\t Stage=None"
657         if tree_node._parent is not None:
658            print "\tParent=" + tree_node._parent._name
659         else:
660            print "\tParent=" + "None"
661         if tree_node._conditional_probability is not None:
662            print "\tConditional probability=%4.4f" % tree_node._conditional_probability
663         else:
664            print "\tConditional probability=" + "***Undefined***"
665         print "\tChildren:"
666         if len(tree_node._children) > 0:
667            for child_node in sorted(tree_node._children, cmp=lambda x,y: cmp(x._name, y._name)):
668               print "\t\t" + child_node._name
669         else:
670            print "\t\tNone"
671         print "\tScenarios:"
672         if len(tree_node._scenarios) == 0:
673            print "\t\tNone"
674         else:
675            for scenario in sorted(tree_node._scenarios, cmp=lambda x,y: cmp(x._name, y._name)):
676               print "\t\t" + scenario._name
677         print ""
678      print "----------------------------------------------------"
679      print "Stages:"
680      for stage_name in sorted(self._stage_map.keys()):
681         stage = self._stage_map[stage_name]
682         print "\tName=" + stage_name
683         print "\tTree Nodes: "
684         for tree_node in sorted(stage._tree_nodes, cmp=lambda x,y: cmp(x._name, y._name)):
685            print "\t\t" + tree_node._name
686         print "\tVariables: "
687         for (variable, index_template, indices) in stage._variables:
688            if (len(indices) == 1) and (indices[0] == None):
689               print "\t\t" + variable.name
690            else:
691               print "\t\t",variable.name,":",index_template
692         print "\tCost Variable: "           
693         if stage._cost_variable[1] is None:
694            print "\t\t" + stage._cost_variable[0].name
695         else:
696            print "\t\t" + stage._cost_variable[0].name + indexToString(stage._cost_variable[1])
697         print ""           
698      print "----------------------------------------------------"
699      print "Scenarios:"
700      for scenario_name in sorted(self._scenario_map.keys()):
701         scenario = self._scenario_map[scenario_name]
702         print "\tName=" + scenario_name
703         print "\tProbability=%4.4f" % scenario._probability
704         if scenario._leaf_node is None:
705            print "\tLeaf node=None"
706         else:
707            print "\tLeaf node=" + scenario._leaf_node._name
708         print "\tTree node sequence:"
709         for tree_node in scenario._node_list:
710            print "\t\t" + tree_node._name
711         print ""                       
712      print "----------------------------------------------------"
713
714   #
715   # a utility function to pretty-print the solution associated with a scenario tree
716   #
717
718   def pprintSolution(self):
719
720      print "----------------------------------------------------"         
721      print "Tree Nodes:"
722      print ""
723      for tree_node_name in sorted(self._tree_node_map.keys()):
724         tree_node = self._tree_node_map[tree_node_name]
725         print "\tName=" + tree_node_name
726         if tree_node._stage is not None:
727            print "\tStage=" + tree_node._stage._name
728         else:
729            print "\t Stage=None"
730         if tree_node._parent is not None:
731            print "\tParent=" + tree_node._parent._name
732         else:
733            print "\tParent=" + "None"
734         print "\tVariables: "
735         for (variable, index_template, indices) in tree_node._stage._variables:
736            solution_variable = tree_node._solutions[variable.name]
737            if (len(indices) == 1) and (indices[0] == None):
738               # if this is a singleton variable, then it should necessarily be active -
739               # otherwise, it wouldn't be referenced in the stage!!!
740               print "\t\t"+variable.name+"="+str(solution_variable[index]())
741            else:
742               for index in indices:
743                  if solution_variable[index].active is True:
744                     print "\t\t"+variable.name+indexToString(index)+"="+str(solution_variable[index]())
745         print ""           
746
747   #
748   # a utility function to pretty-print the cost information associated with a scenario tree
749   #
750
751   def pprintCosts(self, scenario_instance_map):
752
753      print "Scenario Tree Costs"
754      print "***WARNING***: Assumes full (or nearly so) convergence of scenario solutions at each node in the scenario tree - computed costs are invalid otherwise"
755
756      print "----------------------------------------------------"
757      if self._reference_instance is not None:
758         print "Model=" + self._reference_instance.name
759      else:
760         print "Model=" + "Unassigned"
761
762      print "----------------------------------------------------"     
763      print "Tree Nodes:"
764      print ""
765      for tree_node_name in sorted(self._tree_node_map.keys()):
766         tree_node = self._tree_node_map[tree_node_name]
767         print "\tName=" + tree_node_name
768         if tree_node._stage is not None:
769            print "\tStage=" + tree_node._stage._name
770         else:
771            print "\t Stage=None"
772         if tree_node._parent is not None:
773            print "\tParent=" + tree_node._parent._name
774         else:
775            print "\tParent=" + "None"
776         if tree_node._conditional_probability is not None:
777            print "\tConditional probability=%4.4f" % tree_node._conditional_probability
778         else:
779            print "\tConditional probability=" + "***Undefined***"
780         print "\tChildren:"
781         if len(tree_node._children) > 0:
782            for child_node in sorted(tree_node._children, cmp=lambda x,y: cmp(x._name, y._name)):
783               print "\t\t" + child_node._name
784         else:
785            print "\t\tNone"
786         print "\tScenarios:"
787         if len(tree_node._scenarios) == 0:
788            print "\t\tNone"
789         else:
790            for scenario in sorted(tree_node._scenarios, cmp=lambda x,y: cmp(x._name, y._name)):
791               print "\t\t" + scenario._name
792         print "\tExpected node cost=%10.4f" % tree_node.computeExpectedNodeCost(scenario_instance_map)
793         print ""
794
795      print "----------------------------------------------------"
796      print "Scenarios:"
797      print ""
798      for scenario_name, scenario in sorted(self._scenario_map.iteritems()):
799         instance = scenario_instance_map[scenario_name]
800
801         print "\tName=" + scenario_name
802         print "\tProbability=%4.4f" % scenario._probability
803
804         if scenario._leaf_node is None:
805            print "\tLeaf Node=None"
806         else:
807            print "\tLeaf Node=" + scenario._leaf_node._name
808
809         print "\tTree node sequence:"
810         for tree_node in scenario._node_list:
811            print "\t\t" + tree_node._name
812
813         aggregate_cost = 0.0
814         for stage in self._stages:
815            instance_cost_variable = instance.active_components(Var)[stage._cost_variable[0].name][stage._cost_variable[1]]()
816            print "\tStage=%20s     Cost=%10.4f" % (stage._name, instance_cost_variable)
817            aggregate_cost += instance_cost_variable
818         print "\tTotal scenario cost=%10.4f" % aggregate_cost
819         print ""
820      print "----------------------------------------------------"     
Note: See TracBrowser for help on using the repository browser.