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

Last change on this file since 2446 was 2446, checked in by jwatson, 9 years ago

Added --output-scenario-tree-solution option to PySP runph script, which will:
1) Create a solution from the node averages in a scenario tree -and-
2) Output the full solution in scenario tree format (which includes the leaf nodes).

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