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

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

Updates to allow for proper handling of variable maps in cases where formats cannot handle fully qualified,
human-readable variable names. Many ripple effects, as problem writers now must return a variable map (or None,
if not applicable. CBC now properly handles and reads NL and SOL input/output combinations!

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)
328        else:
329           return (args,ProblemFormat.mps)
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.