source: coopr.opt/trunk/coopr/opt/base/solver.py @ 2667

Last change on this file since 2667 was 2667, checked in by prsteel, 11 years ago

Added empty capabilities dict to 'OptSolver?' base class.

Before, the base class did not define self._capabilities, and so would
throw an error when 'has_capabilities' was called. OptSolver? now
defines an empty self._capabilities container to avoid this.

File size: 9.2 KB
Line 
1#  _________________________________________________________________________
2#
3#  Coopr: A COmmon Optimization Python Repository
4#  Copyright (c) 2008 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__all__ = ['IOptSolver', 'OptSolver', 'SolverFactory']
12
13import os
14import sys
15from convert import convert_problem
16from formats import ResultsFormat, ProblemFormat
17import results
18from coopr.opt.results import SolverResults, SolverStatus
19
20from pyutilib.enum import Enum
21from pyutilib.component.core import *
22from pyutilib.component.config import *
23import pyutilib.common
24import pyutilib.misc
25import time
26
27
28class IOptSolver(Interface):
29    """Interface class for creating optimization solvers"""
30
31    def available(self, exception_flag=True):
32        """Determine if this optimizer is available."""
33
34    def warm_start_capable(self):
35       """ True is the solver can accept a warm-start solution."""       
36
37    def solve(self, *args, **kwds):
38        """Perform optimization and return an SolverResults object."""
39
40    def reset(self):
41        """Reset the state of an optimizer"""
42
43    def set_options(self, istr):
44        """Set the options in the optimizer from a string."""
45
46
47SolverFactory = CreatePluginFactory(IOptSolver)
48
49
50class OptSolver(ManagedPlugin):
51    """A generic optimization solver"""
52
53    implements(IOptSolver)
54
55    def __init__(self, **kwds):
56        """ Constructor """
57        ManagedPlugin.__init__(self,**kwds)
58        #
59        # The 'type' is the class type of the solver instance
60        #
61        if "type" in kwds:
62            self.type = kwds["type"]
63        else:                           #pragma:nocover
64            raise PluginError, "Expected option 'type' for OptSolver constructor"
65        #
66        # The 'name' is either the class type of the solver instance, or a
67        # assigned name.
68        #
69        if "name" in kwds:
70            self.name = kwds["name"]
71        else:
72            self.name = self.type
73        if "doc" in kwds:
74            self._doc = kwds["doc"]
75        else:
76            if self.type is None:           # pragma:nocover
77                self._doc = ""
78            elif self.name == self.type:
79                self._doc = "%s OptSolver" % self.name
80            else:
81                self._doc = "%s OptSolver (type %s)" % (self.name,self.type)
82        declare_option("options", cls=DictOption, section=self.name, doc=self._doc)
83        if 'options' in kwds:
84            for key in kwds['options']:
85                setattr(self.options,key,kwds['options'][key])
86        self._symbol_map=None
87        self._problem_format=None
88        self._results_format=None
89        self._valid_problem_formats=[]
90        self._valid_result_formats={}
91        self.results_reader=None
92        self.problem=None
93        self._assert_available=False
94        self._report_timing = False # timing statistics are always collected, but optionally reported.
95        self.suffixes = [] # a list of the suffixes the user has request be loaded in a solution.
96
97        # a fairly universal solver feature, at least when dealing
98        # with problems containing integers. promoted to a base class
99        # attribute to allow each sub-solver to automatically write
100        # out the appropriate option. default is None, meaning
101        # unassigned. currently not allowing the mipgap to be over-ridden
102        # via an argument to the solve() method, mainly because we don't
103        # want to persistence of this parameter to be violated.
104        self.mipgap = None
105
106        # We define no capabilities for the generic solver; base classes must override this
107        self._capabilities = pyutilib.misc.Options()
108
109    def has_capability(self, cap):
110        """
111        Returns a boolean value representing whether a solver supports
112        a specific feature. Defaults to 'False' if the solver is unaware
113        of an option. Expects a string.
114
115        Example:
116        print solver.sos1 # prints True if solver supports sos1 constraints,
117                          # and False otherwise
118        print solver.feature # prints True is solver supports 'feature', and
119                             # False otherwise
120        """
121        if not isinstance(cap, str):
122            raise TypeError, "Expected argument to be of type '%s', not " + \
123                  "'%s'." % (str(type(str())), str(type(cap)))
124        else:
125            val = self._capabilities[str(cap)]
126            if val is None:
127                return False
128            else:
129                return True
130
131    def available(self, exception_flag=True):
132        """ True if the solver is available """
133        if self._assert_available:
134            return True
135        tmp = self.enabled()
136        if exception_flag and not tmp:
137            raise pyutilib.common.ApplicationError, "OptSolver plugin %s is disabled" % self.name
138        return tmp
139
140    def warm_start_capable(self):
141       """ True is the solver can accept a warm-start solution """
142       return False
143
144    def solve(self, *args, **kwds):
145        """ Solve the problem """
146        initial_time = time.time()
147        self._presolve(*args, **kwds)
148        presolve_completion_time = time.time()
149        self._apply_solver()
150        solve_completion_time = time.time()
151        result = self._postsolve()
152        postsolve_completion_time = time.time()
153        result.symbol_map = self._symbol_map
154        if self._report_timing is True:
155           print "Presolve time="+str(presolve_completion_time-initial_time)
156           print "Solve time="+str(solve_completion_time - presolve_completion_time)
157           print "Postsolve time="+str(postsolve_completion_time-solve_completion_time)
158        return result
159
160    def _presolve(self, *args, **kwds):
161        self.available()
162        self._timelimit=None
163        self.tee=None
164        for key in kwds:
165          if key == "pformat":
166             self._problem_format=kwds[key]
167          elif key == "rformat":
168             self._results_format=kwds[key]
169          elif key == "logfile":
170             self.log_file=kwds[key]
171          elif key == "solnfile":
172             self.soln_file=kwds[key]
173          elif key == "timelimit":
174             self._timelimit=kwds[key]
175          elif key == "tee":
176             self.tee=kwds[key]
177          elif key == "options":
178             self.set_options(kwds[key])
179          elif key == "available":
180             self._assert_available=True
181          elif key == "suffixes":
182             val = kwds[key]
183             self.suffixes=kwds[key]
184          else:
185             raise ValueError, "Unknown option="+key+" for solver="+self.type
186
187        (self._problem_files,self._problem_format,self._symbol_map) = self._convert_problem(args, self._problem_format, self._valid_problem_formats)
188        if self._results_format is None:
189           self._results_format= self._default_results_format(self._problem_format)
190        #
191        # Disabling this check for now.  A solver doesn't have just _one_ results format.
192        #
193        #if self._results_format not in self._valid_result_formats[self._problem_format]:
194        #   raise ValueError, "Results format `"+str(self._results_format)+"' cannot be used with problem format `"+str(self._problem_format)+"' in solver "+self.name
195        if self._results_format == ResultsFormat.soln:
196            self.results_reader = None
197        else:
198            self.results_reader = results.ReaderFactory(self._results_format)
199
200    def _apply_solver(self):
201        """The routine that performs the solve"""
202        raise NotImplementedError       #pragma:nocover
203       
204    def _postsolve(self):
205        """The routine that does solve post-processing"""
206        return self.results
207       
208    def _convert_problem(self, args, pformat, valid_pformats):
209        #
210        # If the problem is not None, then we assume that it has already
211        # been appropriately defined.  Either it's a string name of the
212        # problem we want to solve, or its a functor object that we can
213        # evaluate directly.
214        #
215        if self.problem is not None:
216           return (self.problem,ProblemFormat.colin_optproblem,None)
217        #
218        # Otherwise, we try to convert the object explicitly.
219        #
220        return convert_problem(args, pformat, valid_pformats)
221
222    def _default_results_format(self, prob_format):
223        """Returns the default results format for different problem
224            formats.
225        """
226        return ResultsFormat.results
227
228    def reset(self):
229        """
230        Reset the state of the solver
231        """
232        pass
233
234    def set_options(self, istr):
235        istr = istr.strip()
236        if istr is '':
237            return
238        tokens = pyutilib.misc.quote_split('[ ]+',istr)
239        for token in tokens:
240            index = token.find('=')
241            if index is -1:
242                raise ValueError, "Solver options must have the form option=value"
243            try:
244                val = eval(token[(index+1):])
245            except:
246                val = token[(index+1):]
247            setattr(self.options, token[:index], val)
248           
249           
250   
Note: See TracBrowser for help on using the repository browser.