source: trunk/coopr/plugins/mip/CBC.py @ 1768

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

Rework of Coopr to use the new PyUtilib? package decomposition.

NOTE: to use Coopr with this update, we need to work with a new version of coopr_install.

File size: 13.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
12import os
13import re
14from coopr.opt.base import *
15from coopr.opt.solver import *
16import pyutilib.services
17import pyutilib.misc
18import pyutilib.common
19import pyutilib.plugin.core
20import mockmip
21
22class CBC(SystemCallSolver):
23    """The CBC LP/MIP solver
24    """
25
26    def __init__(self, **kwds):
27        #
28        # Call base constructor
29        #
30        kwds['type'] = 'cbc'
31        SystemCallSolver.__init__(self, **kwds)
32        #
33        # Setup valid problem formats and valid results for each problem format
34        #
35#        self._valid_problem_formats=[ProblemFormat.nl, ProblemFormat.cpxlp, ProblemFormat.mps]
36        self._valid_problem_formats=[ProblemFormat.cpxlp, ProblemFormat.nl, ProblemFormat.mps]       
37        self._valid_result_formats={}
38        self._valid_result_formats[ProblemFormat.mps] = [ResultsFormat.log]
39        self._valid_result_formats[ProblemFormat.nl] = [ResultsFormat.log]
40        self._valid_result_formats[ProblemFormat.cpxlp] = [ResultsFormat.log]
41
42    def executable(self):
43        executable = pyutilib.services.registered_executable("cbc")
44        if executable is None:
45            pyutilib.plugin.core.PluginGlobals.env().log.error("Could not locate the 'cbc' executable, which is required for solver %s" % self.name)
46            self.enable = False
47            return None
48        return executable.get_path()
49
50    def create_command_line(self,executable,problem_files):
51        #
52        # Define log file
53        #
54        if self.log_file is None:
55           self.log_file = pyutilib.services.TempfileManager.create_tempfile(suffix=".cbc.log")
56        fname = problem_files[0].split(os.sep)[-1]
57        if '.' in fname:
58            tmp = fname.split('.')
59            if len(tmp) > 2:
60                fname = '.'.join(tmp[:-1])
61            else:
62                fname = tmp[0]
63        #
64        # Define solution file
65        #
66        if self.soln_file is None:
67           self.soln_file = pyutilib.services.TempfileManager.create_tempfile(suffix=fname+".cbc.soln")
68        self.sol_file = fname+".sol"
69        #
70        # Define results file
71        #
72        if self._results_format is None or self._results_format == ResultsFormat.log:
73           self.results_file = self.log_file
74        #elif self._results_format == ResultsFormat.osrl:
75        #   self.results_file = self.tmpDir+os.sep+"cbc.osrl.xml"
76
77        #
78        # Define command line (only one problem format supported: MPS)
79        #
80        if self._problem_format == ProblemFormat.nl:
81            if self._timelimit is not None and self._timelimit > 0.0:
82                timing = " sec="+str(self._timelimit)
83            else:
84                timing = ""
85            if "debug" in self.options:
86                opt = ""
87            else:
88                opt = " log=5"
89            for key in self.options:
90                if isinstance(self.options[key],basestring) and ' ' in self.options[key]:
91                    opt += " "+key+"=\""+str(self.options[key])+"\""
92                else:
93                    opt += " "+key+"="+str(self.options[key])
94            self._nl_options = " ".join(self.options) + timing+ " stat= "+opt+" printingOptions=all"
95            proc = self._timer + " " + executable + " " + problem_files[0] + " -AMPL "+self._nl_options
96        else:
97            if self._timelimit is not None and self._timelimit > 0.0:
98                timing = " -sec "+str(self._timelimit)+" "
99            else:
100                timing = ""
101            if "debug" in self.options:
102                opt = ""
103            else:
104                opt = " -log 5"
105            for key in self.options:
106                if isinstance(self.options[key],basestring) and ' ' in self.options[key]:
107                    opt += " -"+key+" \""+str(self.options[key])+"\""
108                else:
109                    opt += " -"+key+" "+str(self.options[key])
110            proc = self._timer + " " + executable + opt + " -printingOptions all "+timing+ " -import "+problem_files[0]+ " -import -stat -solve -solu " + self.soln_file
111        return pyutilib.misc.Bunch(cmd=proc, log_file=self.log_file, env=None)
112
113    def _execute_command(self,cmd):
114        ans = SystemCallSolver._execute_command(self,cmd)
115        if os.path.exists("cbc.soln"):
116           os.rename("cbc.soln",self.soln_file)
117        return ans
118
119    def process_logfile(self):
120        """
121        Process logfile
122        """
123        results = SolverResults()
124        #
125        # Initial values
126        #
127        results.solver.ncreated=0
128        results.solver.nbounded=0
129        soln = results.solution.create()
130        #
131        # Process logfile
132        #
133        OUTPUT = open(self.log_file)
134        output = "".join(OUTPUT.readlines())
135        OUTPUT.close()
136        #
137        # Parse logfile lines
138        #
139        results.problem.sense = ProblemSense.minimize
140        sense=1
141        results.problem.name = None
142        for line in output.split("\n"):
143          tokens = re.split('[ \t]+',line.strip())
144          #print "LINE:", line
145          if len(tokens) == 10 and tokens[0] == "Current" and tokens[1] == "default" and tokens[2] == "(if" and results.problem.name is None:
146             results.problem.name = tokens[-1]
147             if '.' in results.problem.name:
148                parts = results.problem.name.split('.')
149                if len(parts) > 2:
150                    results.problem.name = '.'.join(parts[:-1])
151                else:
152                    results.problem.name = results.problem.name.split('.')[0]
153             if '/' in results.problem.name:
154                results.problem.name = results.problem.name.split('/')[-1]
155             if '\\' in results.problem.name:
156                results.problem.name = results.problem.name.split('\\')[-1]
157          if len(tokens) ==11 and tokens[0] == "Presolve" and tokens[3] == "rows,":
158             results.problem.num_variables = eval(tokens[4])-eval(tokens[5][1:-1])
159             results.problem.num_constraints = eval(tokens[1])-eval(tokens[2][1:-1])
160             results.problem.num_nonzeros = eval(tokens[8])-eval(tokens[9][1:-1])
161             results.problem.num_objectives = "1"
162          if len(tokens) >=9 and tokens[0] == "Problem" and tokens[2] == "has":
163             results.problem.num_variables = tokens[5]
164             results.problem.num_constraints = tokens[3]
165             results.problem.num_nonzeros = tokens[8]
166             results.problem.num_objectives = "1"
167          if len(tokens) == 5 and tokens[3] == "NAME":
168             results.problem.name = tokens[4]
169          if " ".join(tokens) == '### WARNING: CoinLpIO::readLp(): Maximization problem reformulated as minimization':
170             results.problem.sense = ProblemSense.maximize
171             sense = -1
172          if len(tokens) > 3 and tokens[0] == "Presolve" and tokens[6] == "infeasible":
173             soln.status = SolutionStatus.infeasible
174             soln.value=None
175          if len(tokens) > 3 and tokens[0] == "Optimal" and tokens[1] == "objective":
176             soln.status = SolutionStatus.optimal
177             soln.value=tokens[2]
178          if len(tokens) > 6 and tokens[4] == "best" and tokens[5] == "objective":
179             soln.value=tokens[6]
180          if len(tokens) > 9 and tokens[7] == "(best" and tokens[8] == "possible":
181             results.problem.lower_bound=tokens[9]
182             results.problem.lower_bound = results.problem.lower_bound.split(")")[0]
183          if len(tokens) > 12 and tokens[10] == "best" and tokens[11] == "possible":
184             results.problem.lower_bound=tokens[12]
185          if len(tokens) > 3 and tokens[0] == "Result" and tokens[2] == "Finished":
186             soln.status = SolutionStatus.optimal
187             soln.value=tokens[4]
188          if len(tokens) > 10 and tokens[4] == "time" and tokens[9] == "nodes":
189             results.solver.ncreated=tokens[8]
190             results.solver.nbounded=tokens[8]
191             if eval(results.solver.nbounded) > 0:
192                soln.value=tokens[6]
193          if len(tokens) == 5 and tokens[1] == "Exiting" and tokens[4] == "time":
194             soln.status = SolutionStatus.stoppedByLimit
195          if len(tokens) > 8 and tokens[7] == "nodes":
196             results.solver.ncreated=tokens[6]
197             results.solver.nbounded=tokens[6]
198          if len(tokens) == 2 and tokens[0] == "sys":
199             results.solver.systime=tokens[1]
200          if len(tokens) == 2 and tokens[0] == "user":
201             results.solver.usrtime=tokens[1]
202
203        if soln.value == "1e+50":
204           if sense == 1:
205              soln.value="Infinity"
206           else:
207              soln.value="-Infinity"
208        elif not soln.value is None:
209           soln.value = eval(soln.value)*sense
210        if soln.status is SolutionStatus.optimal:
211           soln.gap=0.0
212           if results.problem.sense == ProblemSense.minimize:
213                results.problem.lower_bound = soln.value
214                if "upper_bound" in dir(results.problem):
215                    del results.problem.upper_bound
216           else:
217                results.problem.upper_bound = soln.value
218                if "lower_bound" in dir(results.problem):
219                    del results.problem.lower_bound
220        if results.solver.status is SolverStatus.error:
221           results.solution.delete(0)
222        return results
223
224    def process_other_data(self,results):
225        if os.path.exists(self.sol_file):
226            results_reader = ReaderFactory(ResultsFormat.sol)
227            results = results_reader(self.sol_file, results, results.solution(0))
228            return
229        if not os.path.exists(self.soln_file):
230           return
231        solution = results.solution(0)
232        if solution.status is SolutionStatus.infeasible:
233            # NOTE: CBC _does_ print a solution file.  However, I'm not
234            # sure how to interpret it yet.
235            return
236        results.problem.num_objectives=1
237
238        processing_constraints=None # None means header, True means constraints, False means variables.
239        INPUT = open(self.soln_file,"r")
240        for line in INPUT:
241          tokens = re.split('[ \t]+',line.strip())
242          #print "LINE",line,len(tokens)
243          if tokens[0] in ("Optimal", "Status"):
244             # TBD - this logic isn't correct/complete. flush out as necessary.             
245             continue
246          if tokens[0] == "0": # indicates section start.
247             if processing_constraints is None:
248                processing_constraints = True
249             elif processing_constraints is True:
250                processing_constraints = False
251             else:
252                raise RuntimeError, "CBC encountered unexpected line=("+line.strip()+") in solution file="+self.soln_file+"; constraint and variable sections already processed!"
253
254          if processing_constraints is True:
255             constraint = tokens[1]
256             constraint_ax = eval(tokens[2]) # CBC reports the constraint row times the solution vector - not the slack.
257             constraint_dual = eval(tokens[3])
258
259             # assume that CBC has taken care of rounding - we report any
260             # dual value not precisely equal to 0.0.
261             if constraint_dual != 0:
262                solution.dual.add(constraint, constraint_dual)
263
264          elif processing_constraints is False:
265             variable = tokens[1]
266             variable_value = eval(tokens[2])
267             variable_reduced_cost = eval(tokens[3]) # currently ignored.
268
269             # assume that CBC has taken care of rounding - we report any
270             # variable value not precisely equal to 0.0.
271             if variable_value != 0:
272                solution.variable.add(variable, variable_value)
273          else:
274             raise RuntimeError, "CBC encountered unexpected line=("+line.strip()+") in solution file="+self.soln_file+"; expecting header, but found data!"
275         
276        INPUT.close()
277
278
279class MockCBC(CBC,mockmip.MockMIP):
280    """A Mock CBC solver used for testing
281    """
282
283    def __init__(self, **kwds):
284        try:
285           CBC.__init__(self,**kwds)
286        except pyutilib.common.ApplicationError: #pragma:nocover
287           pass                        #pragma:nocover
288        mockmip.MockMIP.__init__(self,"cbc")
289
290    def available(self, exception_flag=True):
291        return CBC.available(self,exception_flag)
292
293    def create_command_line(self,executable,problem_files):
294        command = CBC.create_command_line(self,executable,problem_files)
295        mockmip.MockMIP.create_command_line(self,executable,problem_files)
296        return command
297
298    def executable(self):
299        return mockmip.MockMIP.executable(self)
300
301    def _execute_command(self,cmd):
302        return mockmip.MockMIP._execute_command(self,cmd)
303
304    def _convert_problem(self,args,pformat,valid_pformats):
305        if pformat in [ProblemFormat.mps, ProblemFormat.cpxlp, ProblemFormat.nl]:
306           return (args,pformat)
307        else:
308           return (args,ProblemFormat.mps)
309
310
311pyutilib.services.register_executable(name="cbc")
312SolverRegistration("cbc", CBC)
313SolverRegistration("_mock_cbc", MockCBC)
Note: See TracBrowser for help on using the repository browser.