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

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

Switched default CBC input file format to CPXLP, primarily because the NL solution reader isn't working. Fixed some issues with the CPXLP-based solution file reader; results for primal and dual values match CPLEX.

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