source: coopr.pysp/trunk/coopr/pysp/phinit.py @ 3168

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

Completing implementation of optional processing of expression simplification in PySP.

  • Property svn:executable set to *
File size: 30.2 KB
Line 
1#  _________________________________________________________________________
2#
3#  Coopr: A COmmon Optimization Python Repository
4#  Copyright (c) 2009 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
11
12import os
13import sys
14
15from optparse import OptionParser, OptionGroup
16
17import pyutilib.services
18import pyutilib.misc
19
20# garbage collection control.
21import gc
22
23# for profiling
24try:
25    import cProfile as profile
26except ImportError:
27    import profile
28import pstats
29
30# for serializing
31import pickle
32
33from coopr.pysp.convergence import *
34from coopr.pysp.scenariotree import *
35from coopr.pysp.ph import *
36from coopr.pysp.ef import *
37from coopr.opt.base import SolverFactory
38from coopr.opt.parallel import SolverManagerFactory
39
40from pyutilib.component.core import ExtensionPoint
41from coopr.pysp.solutionwriter import ISolutionWriterExtension
42
43#
44# utility method to construct an option parser for ph arguments,
45# to be supplied as an argument to the runph method.
46#
47
48def construct_ph_options_parser(usage_string):
49
50   parser = OptionParser()
51   parser.usage = usage_string
52
53   inputOpts       = OptionGroup( parser, 'Input Options' )
54   phOpts          = OptionGroup( parser, 'PH Options' )
55   solverOpts      = OptionGroup( parser, 'Solver Options' )
56   postprocessOpts = OptionGroup( parser, 'Postprocessing Options' )   
57   outputOpts      = OptionGroup( parser, 'Output Options' )
58   otherOpts       = OptionGroup( parser, 'Other Options' )
59   parser.add_option_group( inputOpts )
60   parser.add_option_group( phOpts )
61   parser.add_option_group( solverOpts )
62   parser.add_option_group( postprocessOpts )   
63   parser.add_option_group( outputOpts )
64   parser.add_option_group( otherOpts )
65
66   inputOpts.add_option('-m','--model-directory',
67     help="The directory in which all model (reference and scenario) definitions are stored. Default is \".\".",
68     action="store",
69     dest="model_directory",
70     type="string",
71     default=".")
72   inputOpts.add_option('-i','--instance-directory',
73     help="The directory in which all instance (reference and scenario) definitions are stored. Default is '.'.",
74     action="store",
75     dest="instance_directory",
76     type="string",
77     default=".")
78   inputOpts.add_option('--bounds-cfgfile',
79     help="The name of a configuration script to set variable bound values. Default is None.",
80     action="store",
81     dest="bounds_cfgfile",
82     default=None)
83
84   phOpts.add_option('-r','--default-rho',
85     help="The default (global) rho for all blended variables. Default is 1.",
86     action="store",
87     dest="default_rho",
88     type="float",
89     default=1.0)
90   phOpts.add_option('--rho-cfgfile',
91     help="The name of a configuration script to compute PH rho values. Default is None.",
92     action="store",
93     dest="rho_cfgfile",
94     type="string",
95     default=None)
96   phOpts.add_option('--max-iterations',
97     help="The maximal number of PH iterations. Default is 100.",
98     action="store",
99     dest="max_iterations",
100     type="int",
101     default=100)
102   phOpts.add_option('--termdiff-threshold',
103     help="The convergence threshold used in the term-diff and normalized term-diff convergence criteria. Default is 0.01.",
104     action="store",
105     dest="termdiff_threshold",
106     type="float",
107     default=0.01)
108   phOpts.add_option('--enable-free-discrete-count-convergence',
109     help="Terminate PH based on the free discrete variable count convergence metric. Default is False.",
110     action="store_true",
111     dest="enable_free_discrete_count_convergence",
112     default=False)
113   phOpts.add_option('--enable-normalized-termdiff-convergence',
114     help="Terminate PH based on the normalized termdiff convergence metric. Default is True.",
115     action="store_true",
116     dest="enable_normalized_termdiff_convergence",
117     default=False)
118   phOpts.add_option('--enable-termdiff-convergence',
119     help="Terminate PH based on the termdiff convergence metric. Default is True.",
120     action="store_true",
121     dest="enable_termdiff_convergence",
122     default=True)
123   phOpts.add_option('--free-discrete-count-threshold',
124     help="The convergence threshold used in the criterion based on when the free discrete variable count convergence criterion. Default is 20.",
125     action="store",
126     dest="free_discrete_count_threshold",
127     type="float",
128     default=20)
129   phOpts.add_option('--linearize-nonbinary-penalty-terms',
130     help="Approximate the PH quadratic term for non-binary variables with a piece-wise linear function, using the supplied number of equal-length pieces from each bound to the average",
131     action="store",
132     dest="linearize_nonbinary_penalty_terms",
133     type="int",
134     default=0)
135   phOpts.add_option('--breakpoint-strategy',
136     help="Specify the strategy to distribute breakpoints on the [lb, ub] interval of each variable when linearizing. 0 indicates uniform distribution. 1 indicates breakpoints at the node min and max, uniformly in-between. 2 indicates more aggressive concentration of breakpoints near the observed node min/max.",
137     action="store",
138     dest="breakpoint_strategy",
139     type="int",
140     default=0)   
141   phOpts.add_option('--retain-quadratic-binary-terms',
142     help="Do not linearize PH objective terms involving binary decision variables",
143     action="store_true",
144     dest="retain_quadratic_binary_terms",
145     default=False)
146   phOpts.add_option('--drop-proximal-terms',
147     help="Eliminate proximal terms (i.e., the quadratic penalty terms) from the weighted PH objective. Default is False.",
148     action="store_true",
149     dest="drop_proximal_terms",
150     default=False)
151   phOpts.add_option('--enable-ww-extensions',
152     help="Enable the Watson-Woodruff PH extensions plugin. Default is False.",
153     action="store_true",
154     dest="enable_ww_extensions",
155     default=False)
156   phOpts.add_option('--ww-extension-cfgfile',
157     help="The name of a configuration file for the Watson-Woodruff PH extensions plugin. Default is wwph.cfg.",
158     action="store",
159     dest="ww_extension_cfgfile",
160     type="string",
161     default="")
162   phOpts.add_option('--ww-extension-suffixfile',
163     help="The name of a variable suffix file for the Watson-Woodruff PH extensions plugin. Default is wwph.suffixes.",
164     action="store",
165     dest="ww_extension_suffixfile",
166     type="string",
167     default="")
168   phOpts.add_option('--user-defined-extension',
169     help="The name of a python module specifying a user-defined PH extension plugin.",
170     action="store",
171     dest="user_defined_extension",
172     type="string",
173     default=None)
174   phOpts.add_option("--linearize-expressions",
175     help="EXPERIMENTAL: An option intended for use on linear or mixed-integer models " \
176          "in which expression trees in a model (constraints or objectives) are compacted " \
177          "into a more memory-efficient and concise form. The trees themselves are eliminated. ",
178     action="store_true",
179     dest="linearize_expressions",
180     default=False)
181   phOpts.add_option("--simplify-expressions",
182     help="Enable expression simplification during both model instance creation and any " \
183          "subsequent modifications of the model, e.g., during manipulation of the objective.",
184     action="store_true",
185     dest="simplify_expressions",
186     default=False)
187
188   solverOpts.add_option('--scenario-mipgap',
189     help="Specifies the mipgap for all PH scenario sub-problems",
190     action="store",
191     dest="scenario_mipgap",
192     type="float",
193     default=None)
194   solverOpts.add_option('--scenario-solver-options',
195     help="Solver options for all PH scenario sub-problems",
196     action="append",
197     dest="scenario_solver_options",
198     type="string",
199     default=[])
200   solverOpts.add_option('--solver',
201     help="The type of solver used to solve scenario sub-problems. Default is cplex.",
202     action="store",
203     dest="solver_type",
204     type="string",
205     default="cplex")
206   solverOpts.add_option('--solver-manager',
207     help="The type of solver manager used to coordinate scenario sub-problem solves. Default is serial.",
208     action="store",
209     dest="solver_manager_type",
210     type="string",
211     default="serial")
212   solverOpts.add_option('--disable-warmstarts',
213     help="Disable warm-start of scenario sub-problem solves in PH iterations >= 1. Default is False.",
214     action="store_true",
215     dest="disable_warmstarts",
216     default=False)
217
218   postprocessOpts.add_option('--ef-output-file',
219     help="The name of the extensive form output file (currently only LP format is supported), if writing of the extensive form is enabled. Default is efout.lp.",
220     action="store",
221     dest="ef_output_file",
222     type="string",
223     default="efout.lp")
224   postprocessOpts.add_option('--solve-ef',
225     help="Following write of the extensive form model, solve it.",
226     action="store_true",
227     dest="solve_ef",
228     default=False)
229   postprocessOpts.add_option('--ef-mipgap',
230     help="Specifies the mipgap for the EF solve",
231     action="store",
232     dest="ef_mipgap",
233     type="float",
234     default=None)
235   postprocessOpts.add_option('--ef-solver-options',
236     help="Solver options for the extension form problem",
237     action="append",
238     dest="ef_solver_options",
239     type="string",
240     default=[])   
241   postprocessOpts.add_option('--output-ef-solver-log',
242     help="Output solver log during the extensive form solve",
243     action="store_true",
244     dest="output_ef_solver_log",
245     default=False)
246   
247   outputOpts.add_option('--output-scenario-tree-solution',
248     help="Report the full solution (even leaves) in scenario tree format upon termination. Values represent averages, so convergence is not an issue. Default is False.",
249     action="store_true",
250     dest="output_scenario_tree_solution",
251     default=False)
252   outputOpts.add_option('--output-solver-logs',
253     help="Output solver logs during scenario sub-problem solves",
254     action="store_true",
255     dest="output_solver_logs",
256     default=False)
257   outputOpts.add_option('--output-solver-results',
258     help="Output solutions obtained after each scenario sub-problem solve",
259     action="store_true",
260     dest="output_solver_results",
261     default=False)
262   outputOpts.add_option('--output-times',
263     help="Output timing statistics for various PH components",
264     action="store_true",
265     dest="output_times",
266     default=False)
267   outputOpts.add_option('--report-only-statistics',
268     help="When reporting solutions (if enabled), only output per-variable statistics - not the individual scenario values. Default is False.",
269     action="store_true",
270     dest="report_only_statistics",
271     default=False)
272   outputOpts.add_option('--report-solutions',
273     help="Always report PH solutions after each iteration. Enabled if --verbose is enabled. Default is False.",
274     action="store_true",
275     dest="report_solutions",
276     default=False)
277   outputOpts.add_option('--report-weights',
278     help="Always report PH weights prior to each iteration. Enabled if --verbose is enabled. Default is False.",
279     action="store_true",
280     dest="report_weights",
281     default=False)
282   outputOpts.add_option('--restore-from-checkpoint',
283     help="The name of the checkpoint file from which PH should be initialized. Default is \"\", indicating no checkpoint restoration",
284     action="store",
285     dest="restore_from_checkpoint",
286     type="string",
287     default="")
288   outputOpts.add_option('--solution-writer',
289     help="The plugin invoked to write the scenario tree solution. Defaults to the empty list.",
290     action="append",
291     dest="solution_writer",
292     type="string",
293     default = [])
294   outputOpts.add_option('--suppress-continuous-variable-output',
295     help="Eliminate PH-related output involving continuous variables.",
296     action="store_true",
297     dest="suppress_continuous_variable_output",
298     default=False)
299   outputOpts.add_option('--verbose',
300     help="Generate verbose output for both initialization and execution. Default is False.",
301     action="store_true",
302     dest="verbose",
303     default=False)
304   outputOpts.add_option('--write-ef',
305     help="Upon termination, write the extensive form of the model - accounting for all fixed variables.",
306     action="store_true",
307     dest="write_ef",
308     default=False)
309
310   otherOpts.add_option('--disable-gc',
311     help="Disable the python garbage collecter. Default is False.",
312     action="store_true",
313     dest="disable_gc",
314     default=False)
315   otherOpts.add_option('--keep-solver-files',
316     help="Retain temporary input and output files for scenario sub-problem solves",
317     action="store_true",
318     dest="keep_solver_files",
319     default=False)
320   otherOpts.add_option('--profile',
321     help="Enable profiling of Python code.  The value of this option is the number of functions that are summarized.",
322     action="store",
323     dest="profile",
324     type="int",
325     default=0)
326   otherOpts.add_option('--checkpoint-interval',
327     help="The number of iterations between writing of a checkpoint file. Default is 0, indicating never.",
328     action="store",
329     dest="checkpoint_interval",
330     type="int",
331     default=0)
332   otherOpts.add_option('--traceback',
333     help="When an exception is thrown, show the entire call stack. Ignored if profiling is enabled. Default is False.",
334     action="store_true",
335     dest="traceback",
336     default=False)   
337
338   return parser
339
340#
341# Create the reference model / instance and scenario tree instance for PH.
342# IMPT: This method should be moved into a more generic module - it has nothing
343#       to do with PH, and is used elsewhere (by routines that shouldn't have
344#       to know about PH).
345#
346
347def load_reference_and_scenario_models(options):
348
349   #
350   # create and populate the reference model/instance pair.
351   #
352
353   reference_model = None
354   reference_instance = None
355
356   try:
357      reference_model_filename = os.path.expanduser(options.model_directory)+os.sep+"ReferenceModel.py"
358      if options.verbose is True:
359         print "Scenario reference model filename="+reference_model_filename
360      model_import = pyutilib.misc.import_file(reference_model_filename)
361      if "model" not in dir(model_import):
362         print ""
363         print "***ERROR: Exiting test driver: No 'model' object created in module "+reference_model_filename
364         return None, None, None, None
365
366      if model_import.model is None:
367         print ""
368         print "***ERROR: Exiting test driver: 'model' object equals 'None' in module "+reference_model_filename
369         return None, None, None, None
370
371      reference_model = model_import.model
372   except IOError:
373      print "***ERROR: Failed to load scenario reference model from file="+reference_model_filename
374      return None, None, None, None
375
376   try:
377      reference_instance_filename = os.path.expanduser(options.instance_directory)+os.sep+"ReferenceModel.dat"
378      if options.verbose is True:
379         print "Scenario reference instance filename="+reference_instance_filename
380      reference_instance = reference_model.create(reference_instance_filename, preprocess=False, simplify=options.simplify_expressions)
381      # IMPT: disable canonical representation construction for ASL solvers.
382      #       this is a hack, in that we need to address encodings and
383      #       the like at a more general level.
384      if options.solver_type == "asl":
385         reference_instance.skip_canonical_repn = True
386      else:
387         reference_instance.preprocess()
388     
389   except IOError:
390      print "***ERROR: Failed to load scenario reference instance data from file="+reference_instance_filename
391      return None, None, None, None
392
393   #
394   # create and populate the scenario tree model
395   #
396
397   from coopr.pysp.util.scenariomodels import scenario_tree_model
398   scenario_tree_instance = None
399
400   try:
401      scenario_tree_instance_filename = os.path.expanduser(options.instance_directory)+os.sep+"ScenarioStructure.dat"
402      if options.verbose is True:
403         print "Scenario tree instance filename="+scenario_tree_instance_filename
404      scenario_tree_instance = scenario_tree_model.create(scenario_tree_instance_filename, simplify=options.simplify_expressions)
405   except IOError:
406      print "***ERROR: Failed to load scenario tree reference instance data from file="+scenario_tree_instance_filename
407      return None, None, None, None
408
409   #
410   # construct the scenario tree
411   #
412   scenario_tree = ScenarioTree(scenarioinstance=reference_instance,
413                                scenariotreeinstance=scenario_tree_instance)
414
415   return reference_model, reference_instance, scenario_tree, scenario_tree_instance
416
417#
418# Create a PH object from a (pickl) checkpoint. Experimental at the moment.
419#
420def create_ph_from_checkpoint(options):
421
422   # we need to load the reference model, as pickle doesn't save contents of .py files!
423   try:
424      reference_model_filename = os.path.expanduser(options.model_directory)+os.sep+"ReferenceModel.py"
425      if options.verbose is True:
426         print "Scenario reference model filename="+reference_model_filename
427      model_import = pyutilib.misc.import_file(reference_model_filename)
428      if "model" not in dir(model_import):
429         print "***ERROR: Exiting test driver: No 'model' object created in module "+reference_model_filename
430         return
431
432      if model_import.model is None:
433         print "***ERROR: Exiting test driver: 'model' object equals 'None' in module "+reference_model_filename
434         return None
435
436      reference_model = model_import.model
437   except IOError:
438      print "***ERROR: Failed to load scenario reference model from file="+reference_model_filename
439      return None
440
441   # import the saved state
442
443   try:
444      checkpoint_file = open(options.restore_from_checkpoint,"r")
445      ph = pickle.load(checkpoint_file)
446      checkpoint_file.close()
447
448   except IOError, msg:
449      raise RuntimeError, msg
450
451   # tell PH to build the right solver manager and solver TBD - AND PLUGINS, BUT LATER
452
453   raise RuntimeError, "Checkpoint restoration is not fully supported/tested yet!"
454
455   return ph
456
457#
458# Create a PH object from scratch.
459#
460
461def create_ph_from_scratch(options, reference_model, reference_instance, scenario_tree):
462
463   #
464   # print the input tree for validation/information purposes.
465   #
466   if options.verbose is True:
467      scenario_tree.pprint()
468
469   #
470   # validate the tree prior to doing anything serious
471   #
472   if scenario_tree.validate() is False:
473      print "***ERROR: Scenario tree is invalid****"
474      return None
475   else:
476      if options.verbose is True:
477         print "Scenario tree is valid!"
478
479   #
480   # if any of the ww extension configuration options are specified without the
481   # ww extension itself being enabled, halt and warn the user - this has led
482   # to confusion in the past, and will save user support time.
483   #
484   if len(options.ww_extension_cfgfile) > 0 and options.enable_ww_extensions is False:
485      print "***ERROR: A configuration file was specified for the WW extension module, but the WW extensions are not enabled!"
486      return None
487
488   if len(options.ww_extension_suffixfile) > 0 and options.enable_ww_extensions is False:
489      print "***ERROR: A suffix file was specified for the WW extension module, but the WW extensions are not enabled!"
490      return None
491
492   #
493   # if a breakpoint strategy is specified without linearization eanbled, halt and warn the user.
494   #
495   if (options.breakpoint_strategy > 0) and (options.linearize_nonbinary_penalty_terms == 0):
496      print "***ERROR: A breakpoint distribution strategy was specified, but linearization is not enabled!"
497      return None
498
499   #
500   # deal with any plugins. ww extension comes first currently, followed by an option user-defined plugin.
501   # order only matters if both are specified.
502   #
503   if options.enable_ww_extensions is True:
504
505      from coopr.pysp import wwphextension
506
507      plugin = ExtensionPoint(IPHExtension)
508      if len(options.ww_extension_cfgfile) > 0:
509         plugin.service()._configuration_filename = options.ww_extension_cfgfile
510      if len(options.ww_extension_suffixfile) > 0:
511         plugin.service()._suffix_filename = options.ww_extension_suffixfile
512
513   if options.user_defined_extension is not None:
514      print "Trying to import user-defined PH extension module="+options.user_defined_extension
515      # JPW removed the exception handling logic, as the module importer
516      # can raise a broad array of exceptions.
517      __import__(options.user_defined_extension)
518      print "Module successfully loaded"
519
520   #
521   # construct the convergence "computer" class.
522   #
523   converger = None
524   # go with the non-defaults first, and then with the default.
525   if options.enable_free_discrete_count_convergence is True:
526      converger = NumFixedDiscreteVarConvergence(convergence_threshold=options.free_discrete_count_threshold)
527   elif options.enable_normalized_termdiff_convergence is True:
528      converger = NormalizedTermDiffConvergence(convergence_threshold=options.termdiff_threshold)
529   else:
530      converger = TermDiffConvergence(convergence_threshold=options.termdiff_threshold)
531
532
533   #
534   # construct and initialize PH
535   #
536   ph = ProgressiveHedging(max_iterations=options.max_iterations, \
537                           rho=options.default_rho, \
538                           rho_setter=options.rho_cfgfile, \
539                           bounds_setter=options.bounds_cfgfile, \
540                           solver=options.solver_type, \
541                           solver_manager=options.solver_manager_type, \
542                           output_scenario_tree_solution=options.output_scenario_tree_solution, \
543                           scenario_solver_options=options.scenario_solver_options, \
544                           scenario_mipgap=options.scenario_mipgap, \
545                           keep_solver_files=options.keep_solver_files, \
546                           output_solver_log=options.output_solver_logs, \
547                           output_solver_results=options.output_solver_results, \
548                           verbose=options.verbose, \
549                           report_solutions=options.report_solutions, \
550                           report_weights=options.report_weights, \
551                           report_only_statistics=options.report_only_statistics, \
552                           output_times=options.output_times, \
553                           disable_warmstarts=options.disable_warmstarts,
554                           drop_proximal_terms=options.drop_proximal_terms,
555                           retain_quadratic_binary_terms=options.retain_quadratic_binary_terms, \
556                           linearize_nonbinary_penalty_terms=options.linearize_nonbinary_penalty_terms, \
557                           breakpoint_strategy=options.breakpoint_strategy, \
558                           checkpoint_interval=options.checkpoint_interval, \
559                           simplify_expressions=options.simplify_expressions)
560
561   ph.initialize(scenario_data_directory_name=os.path.expanduser(options.instance_directory), \
562                 model=reference_model, \
563                 model_instance=reference_instance, \
564                 scenario_tree=scenario_tree, \
565                 converger=converger, \
566                 linearize=options.linearize_expressions)
567
568   if options.suppress_continuous_variable_output is True:
569      ph._output_continuous_variable_stats = False # clutters up the screen, when we really only care about the binaries.
570
571   return ph
572
573
574
575
576#
577# Given a PH object, execute it and optionally solve the EF at the end.
578#
579
580def run_ph(options, ph):
581
582   #
583   # at this point, we have an initialized PH object by some means.
584   #
585   start_time = time.time()
586
587   #
588   # kick off the solve
589   #
590   ph.solve()
591
592   end_time = time.time()
593
594   print ""
595   print "Total PH execution time=%8.2f seconds" %(end_time - start_time)
596   print ""
597   if options.output_times is True:
598      ph.print_time_stats()
599
600   solution_writer_plugins = ExtensionPoint(ISolutionWriterExtension)
601   for plugin in solution_writer_plugins:
602      plugin.write(ph._scenario_tree, "ph")
603
604   # store the binding instance, if created, in order to load
605   # the solution back into the scenario tree.
606   binding_instance = None
607
608   #
609   # write the extensive form, accounting (implicitly) for any fixed variables.
610   #
611   if (options.write_ef is True) or (options.solve_ef is True):
612      print ""
613      print "Writing EF for remainder problem"
614      print ""
615      binding_instance = create_and_write_ef(ph._scenario_tree, ph._instances, os.path.expanduser(options.ef_output_file))
616
617   #
618   # solve the extensive form and load the solution back into the PH scenario tree.
619   # contents from the PH solve will obviously be over-written!
620   #
621   if options.solve_ef is True:
622      print ""
623      print "Solving extensive form written to file="+os.path.expanduser(options.ef_output_file)
624      print ""
625
626      ef_solver = SolverFactory(options.solver_type)
627      if ef_solver is None:
628         raise ValueError, "Failed to create solver of type="+options.solver_type+" for use in extensive form solve"
629      if len(options.ef_solver_options) > 0:
630         print "Initializing ef solver with options="+str(options.ef_solver_options)
631         ef_solver.set_options("".join(options.ef_solver_options))
632      if options.ef_mipgap is not None:
633         if (options.ef_mipgap < 0.0) or (options.ef_mipgap > 1.0):
634            raise ValueError, "Value of the mipgap parameter for the EF solve must be on the unit interval; value specified=" + `options.ef_mipgap`
635         else:
636            ef_solver.mipgap = options.ef_mipgap
637
638      ef_solver_manager = SolverManagerFactory(options.solver_manager_type)
639      if ef_solver is None:
640         raise ValueError, "Failed to create solver manager of type="+options.solver_type+" for use in extensive form solve"
641
642      print "Queuing extensive form solve"
643      ef_action_handle = ef_solver_manager.queue(os.path.expanduser(options.ef_output_file), opt=ef_solver, tee=options.output_ef_solver_log)
644      print "Waiting for extensive form solve"
645      ef_results = ef_solver_manager.wait_for(ef_action_handle)
646
647      load_ef_solution(ef_results, binding_instance, ph._instances)
648      ph._scenario_tree.snapshotSolutionFromInstances(ph._instances)
649
650      print ""
651      print "Extensive form solution:"
652      ph._scenario_tree.pprintSolution()
653      print ""
654      print "Extensive form costs:"
655      ph._scenario_tree.pprintCosts(ph._instances)
656
657      solution_writer_plugins = ExtensionPoint(ISolutionWriterExtension)
658      for plugin in solution_writer_plugins:
659         plugin.write(ph._scenario_tree, "postphef")
660
661#
662# The main PH initialization / runner routine. Really only branches based on
663# the construction source - a checkpoint or from scratch.
664#
665
666def exec_ph(options):
667
668   ph = None
669
670   # validate the solution writer plugin exists, to avoid a lot of wasted work.
671   for solution_writer_name in options.solution_writer:
672      print "Trying to import solution writer="+solution_writer_name
673      __import__(solution_writer_name)
674      print "Module successfully loaded"
675
676   # if we are restoring from a checkpoint file, do so - otherwise, construct PH from scratch.
677   if len(options.restore_from_checkpoint) > 0:
678      ph = create_ph_from_checkpoint(options)
679
680   else:
681      reference_model, reference_instance, scenario_tree, scenario_tree_instance = load_reference_and_scenario_models(options)
682      if reference_model is None or reference_instance is None or scenario_tree is None:
683         return
684      ph = create_ph_from_scratch(options, reference_model, reference_instance, scenario_tree)
685
686   if ph is None:
687      print "***FAILED TO CREATE PH OBJECT"
688      return
689
690   run_ph(options, ph)
691
692#
693# the main driver routine for the runph script.
694#
695
696def run(args=None):
697
698    #
699    # Top-level command that executes the extensive form writer.
700    # This is segregated from run_ef_writer to enable profiling.
701    #
702
703    #
704    # Parse command-line options.
705    #
706    try:
707       ph_options_parser = construct_ph_options_parser("runph [options]")
708       (options, args) = ph_options_parser.parse_args(args=args)
709    except SystemExit:
710       # the parser throws a system exit if "-h" is specified - catch
711       # it to exit gracefully.
712       return
713    #
714    # Control the garbage collector - more critical than I would like at the moment.
715    #
716
717    if options.disable_gc is True:
718       gc.disable()
719    else:
720       gc.enable()
721
722    #
723    # Run PH - precise invocation depends on whether we want profiling output.
724    #
725
726    # if an exception is triggered and traceback is enabled, 'ans' won't
727    # have a value and the return statement from this function will flag
728    # an error, masking the stack trace that you really want to see.
729    ans = None       
730
731    if options.profile > 0:
732        #
733        # Call the main PH routine with profiling.
734        #
735        tfile = pyutilib.services.TempfileManager.create_tempfile(suffix=".profile")
736        tmp = profile.runctx('exec_ph(options)',globals(),locals(),tfile)
737        p = pstats.Stats(tfile).strip_dirs()
738        p.sort_stats('time', 'cum')
739        p = p.print_stats(options.profile)
740        p.print_callers(options.profile)
741        p.print_callees(options.profile)
742        p = p.sort_stats('cum','calls')
743        p.print_stats(options.profile)
744        p.print_callers(options.profile)
745        p.print_callees(options.profile)
746        p = p.sort_stats('calls')
747        p.print_stats(options.profile)
748        p.print_callers(options.profile)
749        p.print_callees(options.profile)
750        pyutilib.services.TempfileManager.clear_tempfiles()
751        ans = [tmp, None]
752    else:
753        #
754        # Call the main PH routine without profiling.
755        #
756
757        if options.traceback is True:
758           ans = exec_ph(options)
759        else:
760           try:
761              ans = exec_ph(options)
762           except ValueError, str:
763              print "VALUE ERROR:"
764              print str
765           except TypeError, str:
766              print "TYPE ERROR:"
767              print str
768           except NameError, str:
769              print "NAME ERROR:"
770              print str                           
771           except IOError, str:
772              print "IO ERROR:"
773              print str
774           except pyutilib.common.ApplicationError, str:
775              print "APPLICATION ERROR:"
776              print str
777           except RuntimeError, str:
778              print "RUN-TIME ERROR:"   
779              print str       
780           except:
781              print "Encountered unhandled exception"
782              traceback.print_exc()
783
784    gc.enable()
785
786    return ans
Note: See TracBrowser for help on using the repository browser.