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

Last change on this file since 2445 was 2445, checked in by jwatson, 10 years ago

Refactored PySP scenario tree code, and added initialization code to (correctly) store solutions.

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