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

Last change on this file since 2445 was 2445, checked in by jwatson, 9 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.