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

Last change on this file since 2633 was 2633, checked in by jwatson, 11 years ago

Added warm_start_capable method definition to the IOptSolver interface. Was previously only in the OptSolver? class interface.

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