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

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

Changes to coopr.opt to support SOS constraints.

Added function 'has_capability' to 'OptSolve?', the generic base class
for all solvers. Accepts a string 'st', and returns true if
self._capabilities[st] == True, and False otherwise. Derived classes are
responsible for overriding the contents of self._capabilities.

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    def has_capability(self, cap):
107        """
108        Returns a boolean value representing whether a solver supports
109        a specific feature. Defaults to 'False' if the solver is unaware
110        of an option. Expects a string.
111
112        Example:
113        print solver.sos1 # prints True if solver supports sos1 constraints,
114                          # and False otherwise
115        print solver.feature # prints True is solver supports 'feature', and
116                             # False otherwise
117        """
118        try:
119            # See if self.capabilities has been defined
120            caps = self._capabilities
121        except:
122            raise NotImplementedError, "Generic solver has no capabilities"
123       
124        if not isinstance(cap, str):
125            raise TypeError, "Expected argument to be of type '%s', not " + \
126                  "'%s'." % (str(type(str())), str(type(cap)))
127        else:
128            val = self._capabilities[str(cap)]
129            if val is None:
130                return False
131            else:
132                return True
133
134    def available(self, exception_flag=True):
135        """ True if the solver is available """
136        if self._assert_available:
137            return True
138        tmp = self.enabled()
139        if exception_flag and not tmp:
140            raise pyutilib.common.ApplicationError, "OptSolver plugin %s is disabled" % self.name
141        return tmp
142
143    def warm_start_capable(self):
144       """ True is the solver can accept a warm-start solution """
145       return False
146
147    def solve(self, *args, **kwds):
148        """ Solve the problem """
149        initial_time = time.time()
150        self._presolve(*args, **kwds)
151        presolve_completion_time = time.time()
152        self._apply_solver()
153        solve_completion_time = time.time()
154        result = self._postsolve()
155        postsolve_completion_time = time.time()
156        result.symbol_map = self._symbol_map
157        if self._report_timing is True:
158           print "Presolve time="+str(presolve_completion_time-initial_time)
159           print "Solve time="+str(solve_completion_time - presolve_completion_time)
160           print "Postsolve time="+str(postsolve_completion_time-solve_completion_time)
161        return result
162
163    def _presolve(self, *args, **kwds):
164        self.available()
165        self._timelimit=None
166        self.tee=None
167        for key in kwds:
168          if key == "pformat":
169             self._problem_format=kwds[key]
170          elif key == "rformat":
171             self._results_format=kwds[key]
172          elif key == "logfile":
173             self.log_file=kwds[key]
174          elif key == "solnfile":
175             self.soln_file=kwds[key]
176          elif key == "timelimit":
177             self._timelimit=kwds[key]
178          elif key == "tee":
179             self.tee=kwds[key]
180          elif key == "options":
181             self.set_options(kwds[key])
182          elif key == "available":
183             self._assert_available=True
184          elif key == "suffixes":
185             val = kwds[key]
186             self.suffixes=kwds[key]
187          else:
188             raise ValueError, "Unknown option="+key+" for solver="+self.type
189
190        (self._problem_files,self._problem_format,self._symbol_map) = self._convert_problem(args, self._problem_format, self._valid_problem_formats)
191        if self._results_format is None:
192           self._results_format= self._default_results_format(self._problem_format)
193        #
194        # Disabling this check for now.  A solver doesn't have just _one_ results format.
195        #
196        #if self._results_format not in self._valid_result_formats[self._problem_format]:
197        #   raise ValueError, "Results format `"+str(self._results_format)+"' cannot be used with problem format `"+str(self._problem_format)+"' in solver "+self.name
198        if self._results_format == ResultsFormat.soln:
199            self.results_reader = None
200        else:
201            self.results_reader = results.ReaderFactory(self._results_format)
202
203    def _apply_solver(self):
204        """The routine that performs the solve"""
205        raise NotImplementedError       #pragma:nocover
206       
207    def _postsolve(self):
208        """The routine that does solve post-processing"""
209        return self.results
210       
211    def _convert_problem(self, args, pformat, valid_pformats):
212        #
213        # If the problem is not None, then we assume that it has already
214        # been appropriately defined.  Either it's a string name of the
215        # problem we want to solve, or its a functor object that we can
216        # evaluate directly.
217        #
218        if self.problem is not None:
219           return (self.problem,ProblemFormat.colin_optproblem,None)
220        #
221        # Otherwise, we try to convert the object explicitly.
222        #
223        return convert_problem(args, pformat, valid_pformats)
224
225    def _default_results_format(self, prob_format):
226        """Returns the default results format for different problem
227            formats.
228        """
229        return ResultsFormat.results
230
231    def reset(self):
232        """
233        Reset the state of the solver
234        """
235        pass
236
237    def set_options(self, istr):
238        istr = istr.strip()
239        if istr is '':
240            return
241        tokens = pyutilib.misc.quote_split('[ ]+',istr)
242        for token in tokens:
243            index = token.find('=')
244            if index is -1:
245                raise ValueError, "Solver options must have the form option=value"
246            try:
247                val = eval(token[(index+1):])
248            except:
249                val = token[(index+1):]
250            setattr(self.options, token[:index], val)
251           
252           
253   
Note: See TracBrowser for help on using the repository browser.