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

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

Adding "mipgap" attribute to the base solver class, mirroring the timelimit option. This option is pervasive, and the solver plugins themselves need to know how to write the option.

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