[1010] | 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. |
---|
[1217] | 8 | # For more information, see the Coopr README.txt file. |
---|
[1010] | 9 | # _________________________________________________________________________ |
---|
[882] | 10 | |
---|
[1010] | 11 | |
---|
[892] | 12 | import os |
---|
[882] | 13 | import re |
---|
[1657] | 14 | from coopr.opt.base import * |
---|
[1901] | 15 | from coopr.opt.results import * |
---|
[1657] | 16 | from coopr.opt.solver import * |
---|
[1997] | 17 | from coopr.pyomo.base.var import * |
---|
[892] | 18 | import mockmip |
---|
[1768] | 19 | import pyutilib.services |
---|
| 20 | import pyutilib.common |
---|
| 21 | import pyutilib.misc |
---|
[2201] | 22 | import pyutilib.component.core |
---|
[1157] | 23 | import string |
---|
[1611] | 24 | import re |
---|
[882] | 25 | |
---|
[1609] | 26 | import xml.dom.minidom |
---|
| 27 | |
---|
[2168] | 28 | import time |
---|
[1609] | 29 | |
---|
[2168] | 30 | |
---|
[1359] | 31 | class CPLEX(ILMLicensedSystemCallSolver): |
---|
[882] | 32 | """The CPLEX LP/MIP solver |
---|
| 33 | """ |
---|
| 34 | |
---|
[1244] | 35 | def __init__(self, **kwds): |
---|
[892] | 36 | # |
---|
[1157] | 37 | # Call base class constructor |
---|
[892] | 38 | # |
---|
[1244] | 39 | kwds['type'] = 'cplex' |
---|
[1359] | 40 | ILMLicensedSystemCallSolver.__init__(self, **kwds) |
---|
[1157] | 41 | |
---|
| 42 | # We are currently invoking CPLEX via the command line, with input re-direction. As opposed |
---|
| 43 | # to writing our own ilcplex-based C++ driver. Consequently, we need to define an attribute |
---|
| 44 | # to retain the execution script name. |
---|
| 45 | self.cplex_script_file_name = None |
---|
| 46 | |
---|
[2042] | 47 | # NOTE: eventually both of the following attributes should be migrated to a common base class. |
---|
[1609] | 48 | # is the current solve warm-started? a transient data member to communicate state information |
---|
| 49 | # across the _presolve, _apply_solver, and _postsolve methods. |
---|
| 50 | self.warm_start_solve = False |
---|
| 51 | # related to the above, the temporary name of the MST warm-start file (if any). |
---|
[2042] | 52 | self.warm_start_file_name = None |
---|
[1609] | 53 | |
---|
[892] | 54 | # |
---|
[1157] | 55 | # Define valid problem formats and associated results formats |
---|
| 56 | # |
---|
[1359] | 57 | self._valid_problem_formats=[ProblemFormat.cpxlp, ProblemFormat.mps] |
---|
[892] | 58 | self._valid_result_formats={} |
---|
[1939] | 59 | self._valid_result_formats[ProblemFormat.cpxlp] = [ResultsFormat.soln] |
---|
| 60 | self._valid_result_formats[ProblemFormat.mps] = [ResultsFormat.soln] |
---|
[1157] | 61 | |
---|
[1609] | 62 | # |
---|
| 63 | # ultimately, this utility should go elsewhere - perhaps on the PyomoModel itself. |
---|
| 64 | # in the mean time, it is staying here. |
---|
| 65 | # |
---|
| 66 | def _hasIntegerVariables(self, instance): |
---|
| 67 | |
---|
[1664] | 68 | import coopr.pyomo.base.var |
---|
[1685] | 69 | from coopr.pyomo.base.set_types import IntegerSet, BooleanSet |
---|
[1621] | 70 | |
---|
[1997] | 71 | for variable in instance.active_components(Var).values(): |
---|
[1609] | 72 | |
---|
| 73 | if (isinstance(variable.domain, IntegerSet) is True) or (isinstance(variable.domain, BooleanSet) is True): |
---|
| 74 | |
---|
| 75 | return True |
---|
| 76 | |
---|
| 77 | return False |
---|
| 78 | |
---|
| 79 | # |
---|
[1984] | 80 | # CPLEX has a simple, easy-to-use warm-start capability. |
---|
| 81 | # |
---|
| 82 | def warm_start_capable(self): |
---|
| 83 | return True |
---|
| 84 | |
---|
| 85 | # |
---|
[1609] | 86 | # write a warm-start file in the CPLEX MST format. |
---|
| 87 | # |
---|
| 88 | def write_warmstart_file(self, instance): |
---|
| 89 | |
---|
[1664] | 90 | import coopr.pyomo.base.var |
---|
| 91 | |
---|
[2042] | 92 | self.warm_start_file_name = pyutilib.services.TempfileManager.create_tempfile(suffix = '.cplex.mst') |
---|
[1609] | 93 | |
---|
| 94 | doc = xml.dom.minidom.Document() |
---|
| 95 | root_element = doc.createElement("CPLEXSolution") |
---|
| 96 | root_element.setAttribute("version","1.0") |
---|
| 97 | doc.appendChild(root_element) |
---|
| 98 | |
---|
| 99 | # currently not populated. |
---|
| 100 | header_element = doc.createElement("header") |
---|
| 101 | # currently not populated. |
---|
| 102 | quality_element = doc.createElement("quality") |
---|
| 103 | # definitely populated! |
---|
| 104 | variables_element = doc.createElement("variables") |
---|
| 105 | |
---|
| 106 | root_element.appendChild(header_element) |
---|
| 107 | root_element.appendChild(quality_element) |
---|
| 108 | root_element.appendChild(variables_element) |
---|
| 109 | |
---|
[1614] | 110 | # for each variable, add a child to the variables element. |
---|
| 111 | # both continuous and discrete are accepted (and required, |
---|
| 112 | # depending on other options), according to the CPLEX manual. |
---|
[1609] | 113 | output_index = 0 |
---|
[2006] | 114 | for variable in instance.active_components(Var).values(): |
---|
[1609] | 115 | |
---|
[1614] | 116 | for index in variable._varval.keys(): |
---|
[1609] | 117 | |
---|
[1664] | 118 | if (variable[index].status != coopr.pyomo.base.var.VarStatus.unused) and (variable[index].value != None) and (variable[index].fixed == False): |
---|
[1609] | 119 | |
---|
[1614] | 120 | variable_element = doc.createElement("variable") |
---|
[1684] | 121 | name = variable[index].label |
---|
| 122 | name = name.replace('[','(') |
---|
| 123 | name = name.replace(']',')') |
---|
| 124 | variable_element.setAttribute("name", name) |
---|
[1614] | 125 | variable_element.setAttribute("index", str(output_index)) |
---|
| 126 | variable_element.setAttribute("value", str(variable[index].value)) |
---|
[1609] | 127 | |
---|
[1614] | 128 | variables_element.appendChild(variable_element) |
---|
[1609] | 129 | |
---|
[1614] | 130 | output_index = output_index + 1 |
---|
[1609] | 131 | |
---|
[2042] | 132 | mst_file = open(self.warm_start_file_name,'w') |
---|
[1609] | 133 | doc.writexml(mst_file, indent=" ", newl="\n") |
---|
| 134 | mst_file.close() |
---|
| 135 | |
---|
| 136 | # over-ride presolve to extract the warm-start keyword, if specified. |
---|
| 137 | def _presolve(self, *args, **kwds): |
---|
| 138 | |
---|
[1621] | 139 | # if the first argument is a string (representing a filename), |
---|
| 140 | # then we don't have an instance => the solver is being applied |
---|
| 141 | # to a file. |
---|
[1609] | 142 | |
---|
[1684] | 143 | self.warm_start_solve = False |
---|
[1609] | 144 | if "warmstart" in kwds: |
---|
| 145 | self.warm_start_solve = kwds["warmstart"] |
---|
[1684] | 146 | del kwds["warmstart"] |
---|
[1609] | 147 | |
---|
[1621] | 148 | if (len(args) > 0) and (isinstance(args[0],basestring) is False): |
---|
[1609] | 149 | |
---|
[1621] | 150 | # write the warm-start file - currently only supports MIPs. |
---|
| 151 | # we only know how to deal with a single problem instance. |
---|
| 152 | if self.warm_start_solve is True: |
---|
| 153 | |
---|
| 154 | if len(args) != 1: |
---|
| 155 | raise ValueError, "CPLEX _presolve method can only handle a single problem instance - "+str(len(args))+" were supplied" |
---|
| 156 | |
---|
[2168] | 157 | if self._hasIntegerVariables(args[0]) is True: |
---|
| 158 | start_time = time.time() |
---|
[1621] | 159 | self.write_warmstart_file(args[0]) |
---|
[2168] | 160 | end_time = time.time() |
---|
[2212] | 161 | if self._report_timing is True: |
---|
| 162 | print "Warm start write time="+str(end_time-start_time)+" seconds" |
---|
[1609] | 163 | |
---|
| 164 | # let the base class handle any remaining keywords/actions. |
---|
[1684] | 165 | ILMLicensedSystemCallSolver._presolve(self, *args, **kwds) |
---|
[1609] | 166 | |
---|
[1167] | 167 | def executable(self): |
---|
[1768] | 168 | executable = pyutilib.services.registered_executable("cplex") |
---|
[1167] | 169 | if executable is None: |
---|
[2201] | 170 | pyutilib.component.core.PluginGlobals.env().log.error("Could not locate the 'cplex' executable, which is required for solver %s" % self.name) |
---|
[1161] | 171 | self.enable = False |
---|
[1171] | 172 | return None |
---|
| 173 | return executable.get_path() |
---|
[882] | 174 | |
---|
[1492] | 175 | def create_command_line(self,executable,problem_files): |
---|
[882] | 176 | # |
---|
| 177 | # Define log file |
---|
[1157] | 178 | # The log file in CPLEX contains the solution trace, but the solver status can be found in the solution file. |
---|
[882] | 179 | # |
---|
[1768] | 180 | self.log_file = pyutilib.services.TempfileManager.create_tempfile(suffix = '.cplex.log') |
---|
[1157] | 181 | |
---|
[882] | 182 | # |
---|
[1157] | 183 | # Define solution file |
---|
| 184 | # As indicated above, contains (in XML) both the solution and solver status. |
---|
| 185 | # |
---|
[1768] | 186 | self.soln_file = pyutilib.services.TempfileManager.create_tempfile(suffix = '.cplex.sol') |
---|
[1157] | 187 | |
---|
| 188 | # |
---|
[882] | 189 | # Define results file |
---|
| 190 | # |
---|
[1939] | 191 | if self._results_format is None or self._results_format == ResultsFormat.soln: |
---|
| 192 | self.results_file = self.soln_file |
---|
[1247] | 193 | elif self._results_format == ResultsFormat.sol: |
---|
[1939] | 194 | self.results_file = self.sol_file |
---|
[1157] | 195 | |
---|
[882] | 196 | # |
---|
[1157] | 197 | # Write the CPLEX execution script |
---|
| 198 | # |
---|
[1768] | 199 | self.cplex_script_file_name = pyutilib.services.TempfileManager.create_tempfile(suffix = '.cplex.script') |
---|
[1157] | 200 | cplex_script_file = open(self.cplex_script_file_name,'w') |
---|
| 201 | cplex_script_file.write("set logfile "+self.log_file+"\n") |
---|
| 202 | if self._timelimit is not None and self._timelimit > 0.0: |
---|
| 203 | cplex_script_file.write("set timelimit "+`self._timelimit`+"\n") |
---|
[2034] | 204 | if (self.mipgap is not None) and (self.mipgap > 0.0): |
---|
| 205 | cplex_script_file.write("set mip tolerances mipgap "+`self.mipgap`+"\n") |
---|
[1429] | 206 | for key in self.options: |
---|
[1496] | 207 | if key in ['relax_integrality']: |
---|
| 208 | pass |
---|
| 209 | elif isinstance(self.options[key],basestring) and ' ' in self.options[key]: |
---|
[1546] | 210 | opt = " ".join(key.split('_'))+" "+str(self.options[key]) |
---|
[1429] | 211 | else: |
---|
| 212 | opt = " ".join(key.split('_'))+" "+str(self.options[key]) |
---|
| 213 | cplex_script_file.write("set "+opt+"\n") |
---|
[1492] | 214 | cplex_script_file.write("read "+problem_files[0]+"\n") |
---|
[1609] | 215 | |
---|
| 216 | # if we're dealing with an LP, the MST file will be empty. |
---|
[2042] | 217 | if (self.warm_start_solve is True) and (self.warm_start_file_name is not None): |
---|
| 218 | cplex_script_file.write("read "+self.warm_start_file_name+"\n") |
---|
[1609] | 219 | |
---|
[1496] | 220 | if 'relax_integrality' in self.options: |
---|
| 221 | cplex_script_file.write("change problem lp\n") |
---|
[1609] | 222 | |
---|
[1359] | 223 | cplex_script_file.write("display problem stats\n") |
---|
[1157] | 224 | cplex_script_file.write("optimize\n") |
---|
| 225 | cplex_script_file.write("write " + self.soln_file+"\n") |
---|
| 226 | cplex_script_file.write("quit\n") |
---|
| 227 | cplex_script_file.close() |
---|
| 228 | |
---|
[1609] | 229 | # dump the script and warm-start file names for the |
---|
| 230 | # user if we're keeping files around. |
---|
[1473] | 231 | if self.keepFiles: |
---|
| 232 | print "Solver script file=" + self.cplex_script_file_name |
---|
[2042] | 233 | if (self.warm_start_solve is True) and (self.warm_start_file_name is not None): |
---|
| 234 | print "Solver warm-start file=" + self.warm_start_file_name |
---|
[1473] | 235 | |
---|
[1157] | 236 | # |
---|
[882] | 237 | # Define command line |
---|
| 238 | # |
---|
[892] | 239 | if self._problem_format in [ProblemFormat.cpxlp, ProblemFormat.mps]: |
---|
[1194] | 240 | proc = self._timer + " " + self.executable() + " < " + self.cplex_script_file_name |
---|
[1768] | 241 | return pyutilib.misc.Bunch(cmd=proc, log_file=self.log_file, env=None) |
---|
[882] | 242 | |
---|
| 243 | def process_logfile(self): |
---|
[892] | 244 | """ |
---|
| 245 | Process logfile |
---|
| 246 | """ |
---|
[882] | 247 | results = SolverResults() |
---|
[1919] | 248 | results.problem.number_of_variables = None |
---|
| 249 | results.problem.number_of_nonzeros = None |
---|
[1347] | 250 | # |
---|
| 251 | # Process logfile |
---|
| 252 | # |
---|
| 253 | OUTPUT = open(self.log_file) |
---|
| 254 | output = "".join(OUTPUT.readlines()) |
---|
| 255 | OUTPUT.close() |
---|
| 256 | # |
---|
[1581] | 257 | # It is generally useful to know the CPLEX version number for logfile parsing. |
---|
| 258 | # |
---|
| 259 | cplex_version = None |
---|
| 260 | |
---|
| 261 | # |
---|
[1347] | 262 | # Parse logfile lines |
---|
| 263 | # |
---|
| 264 | for line in output.split("\n"): |
---|
[1359] | 265 | tokens = re.split('[ \t]+',line.strip()) |
---|
| 266 | if len(tokens) > 3 and tokens[0] == "CPLEX" and tokens[1] == "Error": |
---|
[2168] | 267 | # IMPT: See below - cplex can generate an error line and then terminate fine, e.g., in CPLEX 12.1. |
---|
| 268 | # To handle these cases, we should be specifying some kind of termination criterion always |
---|
| 269 | # in the course of parsing a log file (we aren't doing so currently - just in some conditions). |
---|
[1347] | 270 | results.solver.status=SolverStatus.error |
---|
| 271 | results.solver.error = " ".join(tokens) |
---|
[1581] | 272 | elif len(tokens) >= 3 and tokens[0] == "ILOG" and tokens[1] == "CPLEX": |
---|
| 273 | cplex_version = tokens[2].rstrip(',') |
---|
[1359] | 274 | elif len(tokens) >= 3 and tokens[0] == "Variables": |
---|
[2168] | 275 | if results.problem.number_of_variables is None: # CPLEX 11.2 and subsequent versions have two Variables sections in the log file output. |
---|
[1919] | 276 | results.problem.number_of_variables = tokens[2] |
---|
[1581] | 277 | # In CPLEX 11 (and presumably before), there was only a single line output to |
---|
| 278 | # indicate the constriant count, e.g., "Linear constraints : 16 [Less: 7, Greater: 6, Equal: 3]". |
---|
| 279 | # In CPLEX 11.2 (or somewhere in between 11 and 11.2 - I haven't bothered to track it down |
---|
| 280 | # in that detail), there is another instance of this line prefix in the min/max problem statistics |
---|
| 281 | # block - which we don't care about. In this case, the line looks like: "Linear constraints :" and |
---|
| 282 | # that's all. |
---|
| 283 | elif len(tokens) >= 4 and tokens[0] == "Linear" and tokens[1] == "constraints": |
---|
[1919] | 284 | results.problem.number_of_constraints = tokens[3] |
---|
[1359] | 285 | elif len(tokens) >= 3 and tokens[0] == "Nonzeros": |
---|
[1919] | 286 | if results.problem.number_of_nonzeros is None: # CPLEX 11.2 and subsequent has two Nonzeros sections. |
---|
| 287 | results.problem.number_of_nonzeros = tokens[2] |
---|
[1359] | 288 | elif len(tokens) >= 5 and tokens[4] == "MINIMIZE": |
---|
| 289 | results.problem.sense = ProblemSense.minimize |
---|
| 290 | elif len(tokens) >= 5 and tokens[4] == "MAXIMIZE": |
---|
| 291 | results.problem.sense = ProblemSense.maximize |
---|
[1954] | 292 | elif len(tokens) >= 4 and tokens[0] == "Solution" and tokens[1] == "time" and tokens[2] == "=": |
---|
| 293 | # technically, I'm not sure if this is CPLEX user time or user+system - CPLEX doesn't appear |
---|
| 294 | # to differentiate, and I'm not sure we can always provide a break-down. |
---|
| 295 | results.solver.user_time = eval(tokens[3]) |
---|
[2168] | 296 | elif len(tokens) >= 4 and tokens[0] == "Dual" and tokens[1] == "simplex" and tokens[3] == "Optimal:": |
---|
| 297 | results.solver.termination_condition = TerminationCondition.optimal |
---|
| 298 | results.solver.termination_message = ' '.join(tokens) |
---|
| 299 | elif len(tokens) >= 4 and tokens[0] == "MIP" and tokens[2] == "Integer" and tokens[3] == "infeasible.": |
---|
| 300 | # if CPLEX has previously printed an error message, reduce it to a warning - |
---|
| 301 | # there is a strong indication it recovered, but we can't be sure. |
---|
| 302 | if results.solver.status == SolverStatus.error: |
---|
| 303 | results.solver.status = SolverStatus.warning |
---|
| 304 | else: |
---|
| 305 | results.solver.status = SolverStatus.ok |
---|
[2100] | 306 | results.solver.termination_condition = TerminationCondition.infeasible |
---|
| 307 | results.solver.termination_message = ' '.join(tokens) |
---|
[2168] | 308 | # for the case below, CPLEX sometimes reports "true" optimal (the first case) |
---|
| 309 | # and other times within-tolerance optimal (the second case). |
---|
| 310 | elif (len(tokens) >= 4 and tokens[0] == "MIP" and tokens[2] == "Integer" and tokens[3] == "optimal") or \ |
---|
| 311 | (len(tokens) >= 4 and tokens[0] == "MIP" and tokens[2] == "Integer" and tokens[3] == "optimal,"): |
---|
| 312 | # if CPLEX has previously printed an error message, reduce it to a warning - |
---|
| 313 | # there is a strong indication it recovered, but we can't be sure. |
---|
| 314 | if results.solver.status == SolverStatus.error: |
---|
| 315 | results.solver.status = SolverStatus.warning |
---|
| 316 | else: |
---|
| 317 | results.solver.status = SolverStatus.ok |
---|
| 318 | results.solver.termination_condition = TerminationCondition.optimal |
---|
| 319 | results.solver.termination_message = ' '.join(tokens) |
---|
| 320 | elif len(tokens) >= 3 and tokens[0] == "Presolve" and tokens[2] == "Infeasible.": |
---|
| 321 | # if CPLEX has previously printed an error message, reduce it to a warning - |
---|
| 322 | # there is a strong indication it recovered, but we can't be sure. |
---|
| 323 | if results.solver.status == SolverStatus.error: |
---|
| 324 | results.solver.status = SolverStatus.warning |
---|
| 325 | else: |
---|
| 326 | results.solver.status = SolverStatus.ok |
---|
[2100] | 327 | results.solver.termination_condition = TerminationCondition.infeasible |
---|
| 328 | results.solver.termination_message = ' '.join(tokens) |
---|
[2168] | 329 | elif len(tokens) >= 5 and tokens[0] == "Presolve" and tokens[2] == "Unbounded" and tokens[4] == "infeasible.": |
---|
| 330 | # if CPLEX has previously printed an error message, reduce it to a warning - |
---|
| 331 | # there is a strong indication it recovered, but we can't be sure. |
---|
| 332 | if results.solver.status == SolverStatus.error: |
---|
| 333 | results.solver.status = SolverStatus.warning |
---|
| 334 | else: |
---|
| 335 | results.solver.status = SolverStatus.ok |
---|
[2100] | 336 | # It isn't clear whether we can determine if the problem is unbounded from |
---|
| 337 | # CPLEX's output. |
---|
| 338 | results.solver.termination_condition = TerminationCondition.unbounded |
---|
| 339 | results.solver.termination_message = ' '.join(tokens) |
---|
[882] | 340 | return results |
---|
| 341 | |
---|
[1939] | 342 | def process_soln_file(self,results): |
---|
[2281] | 343 | |
---|
| 344 | # the only suffixes that we extract from CPLEX are |
---|
| 345 | # constraint duals, constraint slacks, and variable |
---|
| 346 | # reduced-costs. scan through the solver suffix list |
---|
| 347 | # and throw an exception if the user has specified |
---|
| 348 | # any others. |
---|
| 349 | extract_duals = False |
---|
[2284] | 350 | extract_slacks = False |
---|
[2281] | 351 | extract_reduced_costs = False |
---|
| 352 | for suffix in self.suffixes: |
---|
| 353 | if suffix == "dual": |
---|
| 354 | extract_duals = True |
---|
[2284] | 355 | elif suffix == "slack": |
---|
| 356 | extract_slacks = True |
---|
[2281] | 357 | elif suffix == "rc": |
---|
| 358 | extract_reduced_costs = True |
---|
| 359 | else: |
---|
| 360 | raise RuntimeError,"***CPLEX solver plugin cannot extract solution suffix="+suffix |
---|
| 361 | |
---|
| 362 | lp_solution = False |
---|
[1157] | 363 | if not os.path.exists(self.soln_file): |
---|
| 364 | return |
---|
[1237] | 365 | |
---|
[1236] | 366 | soln = Solution() |
---|
[1919] | 367 | soln.objective['f'].value=None |
---|
[1157] | 368 | INPUT = open(self.soln_file,"r") |
---|
[1919] | 369 | results.problem.number_of_objectives=1 |
---|
[2048] | 370 | mip_problem=False |
---|
[1157] | 371 | for line in INPUT: |
---|
| 372 | line = line.strip() |
---|
| 373 | line = line.lstrip('<?/') |
---|
| 374 | line = line.rstrip('/>?') |
---|
| 375 | tokens=line.split(' ') |
---|
| 376 | |
---|
| 377 | if tokens[0] == "variable": |
---|
[1195] | 378 | variable_name = None |
---|
| 379 | variable_value = None |
---|
[1965] | 380 | variable_reduced_cost = None |
---|
[2013] | 381 | variable_status = None |
---|
[1195] | 382 | for i in range(1,len(tokens)): |
---|
| 383 | field_name = string.strip(tokens[i].split('=')[0]) |
---|
| 384 | field_value = (string.strip(tokens[i].split('=')[1])).lstrip("\"").rstrip("\"") |
---|
| 385 | if field_name == "name": |
---|
| 386 | variable_name = field_value |
---|
| 387 | elif field_name == "value": |
---|
| 388 | variable_value = field_value |
---|
[2281] | 389 | elif (extract_reduced_costs is True) and (field_name == "reducedCost"): |
---|
[1965] | 390 | variable_reduced_cost = field_value |
---|
[2284] | 391 | elif (extract_reduced_costs is True) and (field_name == "status"): |
---|
[2013] | 392 | variable_status = field_value |
---|
[1965] | 393 | |
---|
[1463] | 394 | # skip the "constant-one" variable, used to capture/retain objective offsets in the CPLEX LP format. |
---|
| 395 | if variable_name != "ONE_VAR_CONSTANT": |
---|
[2065] | 396 | variable = None # cache the solution variable reference, as the getattr is expensive. |
---|
[1919] | 397 | try: |
---|
[2065] | 398 | variable = soln.variable[variable_name] |
---|
| 399 | variable.value = eval(variable_value) |
---|
[1919] | 400 | except: |
---|
[2065] | 401 | variable.value = variable_value |
---|
[2281] | 402 | if (variable_reduced_cost is not None) and (extract_reduced_costs is True): |
---|
[1970] | 403 | try: |
---|
[2065] | 404 | variable.rc = eval(variable_reduced_cost) |
---|
[2013] | 405 | if variable_status is not None: |
---|
| 406 | if variable_status == "LL": |
---|
[2065] | 407 | variable.lrc = eval(variable_reduced_cost) |
---|
[2013] | 408 | else: |
---|
[2065] | 409 | variable.lrc = 0.0 |
---|
[2013] | 410 | if variable_status == "UL": |
---|
[2065] | 411 | variable.urc = eval(variable_reduced_cost) |
---|
[2013] | 412 | else: |
---|
[2065] | 413 | variable.urc = 0.0 |
---|
[1970] | 414 | except: |
---|
[2013] | 415 | raise ValueError, "Unexpected reduced-cost value="+str(variable_reduced_cost)+" encountered for variable="+variable_name |
---|
[2281] | 416 | elif (tokens[0] == "constraint") and ((extract_duals is True) or (extract_slacks is True)): |
---|
[1195] | 417 | constraint_name = None |
---|
| 418 | constraint_dual = None |
---|
[2065] | 419 | constaint = None # cache the solution constraint reference, as the getattr is expensive. |
---|
[1195] | 420 | for i in range(1,len(tokens)): |
---|
| 421 | field_name = string.strip(tokens[i].split('=')[0]) |
---|
| 422 | field_value = (string.strip(tokens[i].split('=')[1])).lstrip("\"").rstrip("\"") |
---|
| 423 | if field_name == "name": |
---|
| 424 | constraint_name = field_value |
---|
[2065] | 425 | constraint = soln.constraint[constraint_name] |
---|
[2281] | 426 | elif (extract_duals is True) and (field_name == "dual"): # for LPs |
---|
[1235] | 427 | # assumes the name field is first. |
---|
[1359] | 428 | if eval(field_value) != 0.0: |
---|
[2065] | 429 | constraint.dual = eval(field_value) |
---|
[2281] | 430 | elif (extract_slacks is True) and (field_name == "slack"): # for MIPs |
---|
[1235] | 431 | # assumes the name field is first. |
---|
[1359] | 432 | if eval(field_value) != 0.0: |
---|
[2065] | 433 | constraint.slack = eval(field_value) |
---|
[1157] | 434 | elif tokens[0].startswith("problemName"): |
---|
[1359] | 435 | filename = (string.strip(tokens[0].split('=')[1])).lstrip("\"").rstrip("\"") |
---|
[1368] | 436 | #print "HERE",filename |
---|
[1359] | 437 | results.problem.name = os.path.basename(filename) |
---|
| 438 | if '.' in results.problem.name: |
---|
| 439 | results.problem.name = results.problem.name.split('.')[0] |
---|
| 440 | tINPUT=open(filename,"r") |
---|
| 441 | for tline in tINPUT: |
---|
| 442 | tline = tline.strip() |
---|
[1368] | 443 | if tline == "": |
---|
| 444 | continue |
---|
| 445 | tokens = re.split('[\t ]+',tline) |
---|
| 446 | if tokens[0][0] in ['\\', '*']: |
---|
| 447 | continue |
---|
| 448 | elif tokens[0] == "NAME": |
---|
| 449 | results.problem.name = tokens[1] |
---|
[1359] | 450 | else: |
---|
[1368] | 451 | sense = tokens[0].lower() |
---|
[1359] | 452 | if sense in ['max','maximize']: |
---|
| 453 | results.problem.sense = ProblemSense.maximize |
---|
| 454 | if sense in ['min','minimize']: |
---|
| 455 | results.problem.sense = ProblemSense.minimize |
---|
| 456 | break |
---|
| 457 | tINPUT.close() |
---|
| 458 | |
---|
[1157] | 459 | elif tokens[0].startswith("objectiveValue"): |
---|
| 460 | objective_value = (string.strip(tokens[0].split('=')[1])).lstrip("\"").rstrip("\"") |
---|
[1919] | 461 | soln.objective['f'].value = objective_value |
---|
[1157] | 462 | elif tokens[0].startswith("solutionStatusString"): |
---|
[1359] | 463 | solution_status = (string.strip(" ".join(tokens).split('=')[1])).lstrip("\"").rstrip("\"") |
---|
[2168] | 464 | if solution_status in ["optimal", "integer optimal solution", "integer optimal, tolerance"]: |
---|
[1157] | 465 | soln.status = SolutionStatus.optimal |
---|
| 466 | soln.gap = 0.0 |
---|
[1359] | 467 | if results.problem.sense == ProblemSense.minimize: |
---|
[1919] | 468 | results.problem.lower_bound = soln.objective['f'].value |
---|
[1359] | 469 | if "upper_bound" in dir(results.problem): |
---|
| 470 | del results.problem.upper_bound |
---|
| 471 | else: |
---|
[1919] | 472 | results.problem.upper_bound = soln.objective['f'].value |
---|
[1359] | 473 | if "lower_bound" in dir(results.problem): |
---|
| 474 | del results.problem.lower_bound |
---|
[2048] | 475 | mip_problem=True |
---|
| 476 | elif tokens[0].startswith("MIPNodes"): |
---|
| 477 | if mip_problem: |
---|
| 478 | n = eval(string.strip(" ".join(tokens).split('=')[1])).lstrip("\"").rstrip("\"") |
---|
| 479 | results.solver.statistics.branch_and_bound.number_of_created_subproblems=n |
---|
| 480 | results.solver.statistics.branch_and_bound.number_of_bounded_subproblems=n |
---|
| 481 | |
---|
[1195] | 482 | |
---|
[1359] | 483 | if not results.solver.status is SolverStatus.error: |
---|
| 484 | results.solution.insert(soln) |
---|
[1157] | 485 | INPUT.close() |
---|
| 486 | |
---|
[1611] | 487 | def _postsolve(self): |
---|
[1474] | 488 | |
---|
[1611] | 489 | # take care of the annoying (and empty) CPLEX temporary files in the current directory. |
---|
| 490 | # this approach doesn't seem overly efficient, but python os module functions don't |
---|
| 491 | # accept regular expression directly. |
---|
| 492 | filename_list = os.listdir(".") |
---|
| 493 | for filename in filename_list: |
---|
| 494 | # CPLEX temporary files come in two flavors - cplex.log and clone*.log. |
---|
| 495 | # the latter is the case for multi-processor environments. |
---|
[1621] | 496 | # IMPT: trap the possible exception raised by the file not existing. |
---|
| 497 | # this can occur in pyro environments where > 1 workers are |
---|
| 498 | # running CPLEX, and were started from the same directory. |
---|
| 499 | # these logs don't matter anyway (we redirect everything), |
---|
| 500 | # and are largely an annoyance. |
---|
| 501 | try: |
---|
| 502 | if re.match('cplex\.log', filename) != None: |
---|
| 503 | os.remove(filename) |
---|
| 504 | elif re.match('clone\d+\.log', filename) != None: |
---|
| 505 | os.remove(filename) |
---|
| 506 | except OSError: |
---|
| 507 | pass |
---|
[1157] | 508 | |
---|
[1611] | 509 | # let the base class deal with returning results. |
---|
| 510 | return ILMLicensedSystemCallSolver._postsolve(self) |
---|
[1157] | 511 | |
---|
[1609] | 512 | |
---|
[892] | 513 | class MockCPLEX(CPLEX,mockmip.MockMIP): |
---|
| 514 | """A Mock CPLEX solver used for testing |
---|
| 515 | """ |
---|
| 516 | |
---|
[1244] | 517 | def __init__(self, **kwds): |
---|
[892] | 518 | try: |
---|
[1244] | 519 | CPLEX.__init__(self, **kwds) |
---|
[1768] | 520 | except pyutilib.common.ApplicationError: #pragma:nocover |
---|
[892] | 521 | pass #pragma:nocover |
---|
| 522 | mockmip.MockMIP.__init__(self,"cplex") |
---|
| 523 | |
---|
| 524 | def available(self, exception_flag=True): |
---|
| 525 | return CPLEX.available(self,exception_flag) |
---|
| 526 | |
---|
[1492] | 527 | def create_command_line(self,executable,problem_files): |
---|
| 528 | command = CPLEX.create_command_line(self,executable,problem_files) |
---|
| 529 | mockmip.MockMIP.create_command_line(self,executable,problem_files) |
---|
[1109] | 530 | return command |
---|
[892] | 531 | |
---|
[1167] | 532 | def executable(self): |
---|
| 533 | return mockmip.MockMIP.executable(self) |
---|
[892] | 534 | |
---|
| 535 | def _execute_command(self,cmd): |
---|
| 536 | return mockmip.MockMIP._execute_command(self,cmd) |
---|
| 537 | |
---|
| 538 | |
---|
[1768] | 539 | pyutilib.services.register_executable(name="cplex") |
---|
[1161] | 540 | SolverRegistration("cplex", CPLEX) |
---|
| 541 | SolverRegistration("_mock_cplex", MockCPLEX) |
---|