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

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

Changing runef and runph scripts to use the "--flatten-expressions" option instead of the existing "--linearize-expressions" option - which is confusing, given that we do "real" linearization within PySP.

  • Property svn:executable set to *
File size: 13.3 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("--flatten-expressions", "--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.