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

Last change on this file since 2616 was 2616, checked in by wehart, 11 years ago

Bug fix: setting 'suffixes' attribute instead of '_suffixes'

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