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

Last change on this file since 3482 was 3482, checked in by khunter, 9 years ago

Dynamic solver list, minor import fixups.

Make the --help options show the list of available solvers, like pyomo --help.

Also some driveby option additions:

-k (--keep-solver-solutions)
-m (--model-directory)
-i (--instance-directory)

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