source: coopr.pysp/trunk/coopr/pysp/ef_writer_script.py @ 3261

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

Adding two options to the runef and runph pysp scripts, to facilitate scenario downsampling - the case where you have a big tree, but you don't want to use it all.

The options are:
--scenario-tree-downsample-fraction=X
--scenario-tree-random-seed

The options are fairly self-explanatory - the only possible nuance is that the downsample fraction is the fraction of scenarios retained.

  • Property svn:executable set to *
File size: 13.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 sys
13import os
14from optparse import OptionParser, OptionGroup
15
16import pyutilib.services
17import textwrap
18import traceback
19try:
20    import cProfile as profile
21except ImportError:
22    import profile
23import pstats
24import gc
25
26from coopr.pysp.ef import *
27
28from coopr.opt.base import SolverFactory
29from coopr.opt.parallel import SolverManagerFactory
30
31from pyutilib.component.core import ExtensionPoint
32from coopr.pysp.solutionwriter import ISolutionWriterExtension
33
34#
35# utility method to construct an option parser for ef writer arguments
36#
37
38def construct_ef_writer_options_parser(usage_string):
39
40   parser = OptionParser()
41   parser.usage=usage_string
42
43   inputOpts        = OptionGroup( parser, 'Input Options' )
44   scenarioTreeOpts = OptionGroup( parser, 'Scenario Tree Options' )   
45   efOpts           = OptionGroup( parser, 'EF Options' )
46   solverOpts       = OptionGroup( parser, 'Solver Options' )
47   outputOpts       = OptionGroup( parser, 'Output Options' )
48   otherOpts        = OptionGroup( parser, 'Other Options' )
49   parser.add_option_group( inputOpts )
50   parser.add_option_group( scenarioTreeOpts )
51   parser.add_option_group( efOpts )
52   parser.add_option_group( solverOpts )
53   parser.add_option_group( outputOpts )
54   parser.add_option_group( otherOpts )
55
56   inputOpts.add_option('-i','--instance-directory',
57     help='The directory in which all instance (reference and scenario) definitions are stored. Default is ".".',
58     action='store',
59     dest='instance_directory',
60     type='string',
61     default='.')
62   inputOpts.add_option('-m','--model-directory',
63     help='The directory in which all model (reference and scenario) definitions are stored. Default is ".".',
64     action='store',
65     dest='model_directory',
66     type='string',
67     default='.')
68
69   scenarioTreeOpts.add_option('--scenario-tree-seed',
70     help="The random seed associated with manipulation operations on the scenario tree (e.g., down-sampling). Default is 0, indicating unassigned.",
71     action="store",
72     dest="scenario_tree_random_seed",
73     type="int",
74     default=None)
75   scenarioTreeOpts.add_option('--scenario-tree-downsample-fraction',
76     help="The proportion of the scenarios in the scenario tree that are actually used. Specific scenarios are selected at random. Default is 1.0, indicating no down-sampling.",
77     action="store",
78     dest="scenario_tree_downsample_fraction",
79     type="float",
80     default=1.0)   
81
82   efOpts.add_option('--cvar-weight',
83     help='The weight associated with the CVaR term in the risk-weighted objective formulation. Default is 1.0. If the weight is 0, then *only* a non-weighted CVaR cost will appear in the EF objective - the expected cost component will be dropped.',
84     action='store',
85     dest='cvar_weight',
86     type='float',
87     default=1.0)
88   efOpts.add_option('--generate-weighted-cvar',
89     help='Add a weighted CVaR term to the primary objective',
90     action='store_true',
91     dest='generate_weighted_cvar',
92     default=False)
93   efOpts.add_option('--risk-alpha',
94     help='The probability threshold associated with cvar (or any future) risk-oriented performance metrics. Default is 0.95.',
95     action='store',
96     dest='risk_alpha',
97     type='float',
98     default=0.95)
99   efOpts.add_option("--linearize-expressions",
100     help="EXPERIMENTAL: An option intended for use on linear or mixed-integer models " \
101          "in which expression trees in a model (constraints or objectives) are compacted " \
102          "into a more memory-efficient and concise form. The trees themselves are eliminated. ",
103     action="store_true",
104     dest="linearize_expressions",
105     default=False)
106
107   solverOpts.add_option('--mipgap',
108     help='Specifies the mipgap for the EF solve.',
109     action='store',
110     dest='mipgap',
111     type='float',
112     default=None)
113   solverOpts.add_option('--solve',
114     help='Following write of the extensive form model, solve it.',
115     action='store_true',
116     dest='solve_ef',
117     default=False)
118   solverOpts.add_option('--solver',
119     help='The type of solver used to solve scenario sub-problems. Default is cplex.',
120     action='store',
121     dest='solver_type',
122     type='string',
123     default='cplex')
124   solverOpts.add_option('--solver-manager',
125     help='The type of solver manager used to coordinate scenario sub-problem solves. Default is serial.',
126     action='store',
127     dest='solver_manager_type',
128     type='string',
129     default='serial')
130   solverOpts.add_option('--solver-options',
131     help='Solver options for the extension form problem.',
132     action='append',
133     dest='solver_options',
134     type='string',
135     default=[])
136
137   outputOpts.add_option('--output-file',
138     help='Specify the name of the extensive form output file',
139     action='store',
140     dest='output_file',
141     type='string',
142     default='efout.lp')
143   outputOpts.add_option('--output-solver-log',
144     help='Output solver log during the extensive form solve.',
145     action='store_true',
146     dest='output_solver_log',
147     default=False)
148   outputOpts.add_option('--solution-writer',
149     help='The plugin invoked to write the scenario tree solution. Defaults to the empty list.',
150     action='append',
151     dest='solution_writer',
152     type='string',
153     default = [])
154   outputOpts.add_option('--verbose',
155     help='Generate verbose output, beyond the usual status output. Default is False.',
156     action='store_true',
157     dest='verbose',
158     default=False)
159
160   otherOpts.add_option('--disable-gc',
161     help='Disable the python garbage collecter. Default is False.',
162     action='store_true',
163     dest='disable_gc',
164     default=False)
165   otherOpts.add_option('--keep-solver-files',
166     help='Retain temporary input and output files for solve.',
167     action='store_true',
168     dest='keep_solver_files',
169     default=False)
170   otherOpts.add_option('--profile',
171     help='Enable profiling of Python code.  The value of this option is the number of functions that are summarized.',
172     action='store',
173     dest='profile',
174     default=0)
175   otherOpts.add_option('--traceback',
176     help='When an exception is thrown, show the entire call stack. Ignored if profiling is enabled. Default is False.',
177     action='store_true',
178     dest='traceback',
179     default=False)
180
181   return parser
182
183
184def run_ef_writer(options, args):
185
186   # if the user enabled the addition of the weighted cvar term to the objective,
187   # then validate the associated parameters.
188   generate_weighted_cvar = False
189   cvar_weight = None
190   risk_alpha = None
191
192   if options.generate_weighted_cvar is True:
193
194      generate_weighted_cvar = True
195      cvar_weight = options.cvar_weight
196      risk_alpha = options.risk_alpha
197
198   # validate the solution writer plugin exists, to avoid a lot of wasted work.
199   for solution_writer_name in options.solution_writer:
200      print "Trying to import solution writer="+solution_writer_name
201      __import__(solution_writer_name)
202      print "Module successfully loaded"
203
204   scenario_tree, binding_instance, scenario_instances = write_ef_from_scratch(os.path.expanduser(options.model_directory),
205                                                                               os.path.expanduser(options.instance_directory),
206                                                                               os.path.expanduser(options.output_file),
207                                                                               options.verbose,
208                                                                               options.linearize_expressions,
209                                                                               options.scenario_tree_downsample_fraction,
210                                                                               options.scenario_tree_random_seed,
211                                                                               generate_weighted_cvar, cvar_weight, risk_alpha)
212
213   if (scenario_tree is None) or (binding_instance is None) or (scenario_instances is None):
214      raise RuntimeError, "Failed to write extensive form."
215
216   if options.solve_ef is True:
217
218      ef_solver = SolverFactory(options.solver_type)
219      if ef_solver is None:
220         raise ValueError, "Failed to create solver of type="+options.solver_type+" for use in extensive form solve"
221      if len(options.solver_options) > 0:
222         print "Initializing ef solver with options="+str(options.solver_options)
223         ef_solver.set_options("".join(options.solver_options))
224      if options.mipgap is not None:
225         if (options.mipgap < 0.0) or (options.mipgap > 1.0):
226            raise ValueError, "Value of the mipgap parameter for the EF solve must be on the unit interval; value specified=" + `options.mipgap`
227         else:
228            ef_solver.mipgap = options.mipgap
229      if options.keep_solver_files is True:
230         ef_solver.keepFiles = True
231
232      ef_solver_manager = SolverManagerFactory(options.solver_manager_type)
233      if ef_solver is None:
234         raise ValueError, "Failed to create solver manager of type="+options.solver_type+" for use in extensive form solve"
235
236      # at this point you have a specific solver - communicate solver capabilities
237      # to the writer via the instance.
238      binding_instance.has_capability = ef_solver.has_capability
239      for scenario_name, scenario_instance in scenario_instances.items():
240         scenario_instance.has_capability = ef_solver.has_capability
241
242      print "Queuing extensive form solve"
243      ef_action_handle = ef_solver_manager.queue(os.path.expanduser(options.output_file), opt=ef_solver, tee=options.output_solver_log)
244      print "Waiting for extensive form solve"
245      ef_results = ef_solver_manager.wait_for(ef_action_handle)
246      load_ef_solution(ef_results, binding_instance, scenario_instances)
247      scenario_tree.snapshotSolutionFromInstances(scenario_instances)
248
249      # handle output of solution from the scenario tree.
250      print ""
251      print "Extensive form solution:"
252      scenario_tree.pprintSolution()
253      print ""
254      print "Extensive form costs:"
255      scenario_tree.pprintCosts(scenario_instances)
256
257      solution_writer_plugins = ExtensionPoint(ISolutionWriterExtension)
258      for plugin in solution_writer_plugins:
259         plugin.write(scenario_tree, "ef")
260
261def run(args=None):
262
263    #
264    # Top-level command that executes the extensive form writer.
265    # This is segregated from run_ef_writer to enable profiling.
266    #
267
268    #
269    # Parse command-line options.
270    #
271    try:
272       options_parser = construct_ef_writer_options_parser("runef [options]")
273       (options, args) = options_parser.parse_args(args=args)
274    except SystemExit:
275       # the parser throws a system exit if "-h" is specified - catch
276       # it to exit gracefully.
277       return
278
279    if options.disable_gc is True:
280       gc.disable()
281    else:
282       gc.enable()
283
284    # if an exception is triggered and traceback is enabled, 'ans' won't
285    # have a value and the return statement from this function will flag
286    # an error, masking the stack trace that you really want to see.
287    ans = None
288
289    if options.profile > 0:
290        #
291        # Call the main ef writer with profiling.
292        #
293        tfile = pyutilib.services.TempfileManager.create_tempfile(suffix=".profile")
294        tmp = profile.runctx('run_ef_writer(options,args)',globals(),locals(),tfile)
295        p = pstats.Stats(tfile).strip_dirs()
296        p.sort_stats('time', 'cum')
297        options.profile = eval(options.profile)
298        p = p.print_stats(options.profile)
299        p.print_callers(options.profile)
300        p.print_callees(options.profile)
301        p = p.sort_stats('cum','calls')
302        p.print_stats(options.profile)
303        p.print_callers(options.profile)
304        p.print_callees(options.profile)
305        p = p.sort_stats('calls')
306        p.print_stats(options.profile)
307        p.print_callers(options.profile)
308        p.print_callees(options.profile)
309        pyutilib.services.TempfileManager.clear_tempfiles()
310        ans = [tmp, None]
311    else:
312        #
313        # Call the main EF writer without profiling.
314        #
315        if options.traceback is True:
316           ans = run_ef_writer(options, args)
317        else:
318           errmsg = None
319           try:
320              ans = run_ef_writer(options, args)
321           except ValueError, err:
322              errmsg = 'VALUE ERROR: %s' % err
323           except TypeError, err:
324              errmsg = 'TYPE ERROR: %s' % err
325           except NameError, err:
326              errmsg = 'NAME ERROR: %s' % err
327           except IOError, err:
328              errmsg = 'I/O ERROR: %s' % err
329           except RuntimeError, err:
330              errmsg = 'RUN-TIME ERROR: %s' % err
331           except pyutilib.common.ApplicationError, err:
332              errmsg = 'APPLICATION ERROR: %s' % err
333           except Exception, err:
334              errmsg = 'UNKNOWN ERROR: %s' % err
335              traceback.print_exc()
336
337           if errmsg is not None:
338              print >>sys.stderr, errmsg
339
340    gc.enable()
341
342    return ans
343
Note: See TracBrowser for help on using the repository browser.