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

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

Misc bug fixes that were introduced by the introduction of variable_map
data, which is now called symbol_map.

Note: some tests still fail, due to the fact that pico_convert does not
generate symbol mapping information. This is being resolved.

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