#
source:
coopr.pyomo/trunk/coopr/pyomo/io/cpxlp.py
@
4092

Revision 4092, 31.8 KB checked in by jwatson, 5 years ago (diff) |
---|

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 | # |

12 | # Problem Writer for CPLEX LP Format Files |

13 | # |

14 | |

15 | import itertools |

16 | import math |

17 | |

18 | from coopr.opt import ProblemFormat |

19 | from coopr.opt.base import AbstractProblemWriter |

20 | from coopr.pyomo.base import BooleanSet, Constraint, ConstraintList, expr, IntegerSet |

21 | from coopr.pyomo.base import Objective, NumericConstant, SOSConstraint, Var |

22 | from coopr.pyomo.base import VarStatus, value |

23 | from coopr.pyomo.base.numtypes import minimize, maximize |

24 | from coopr.pyomo.expr import is_constant, is_nonlinear, is_quadratic |

25 | |

26 | from pyutilib.component.core import alias |

27 | from pyutilib.misc import deprecated |

28 | |

29 | |

30 | def convert_name(namestr): |

31 | |

32 | return namestr.replace('[','(').replace(']',')') |

33 | |

34 | class ProblemWriter_cpxlp(AbstractProblemWriter): |

35 | |

36 | alias('cpxlp') |

37 | alias('lp') |

38 | |

39 | def __init__(self): |

40 | |

41 | AbstractProblemWriter.__init__(self,ProblemFormat.cpxlp) |

42 | |

43 | # temporarily placing attributes used in extensive-form writing |

44 | # here, for eventual migration to the base class. |

45 | self._output_objectives = True |

46 | self._output_constraints = True |

47 | self._output_variables = True |

48 | # means we're outputting *some* variables - types determined by |

49 | # following flags. |

50 | |

51 | # building on the above, partition out the variables that should |

52 | # be written if the _output_variables attribute is set to True. |

53 | # this is useful when writing components of multiple models to a |

54 | # single LP file. unfortunately, the CPLEX LP format requires all |

55 | # integer and binary (and continuous) variables to appear in a |

56 | # single continuous block. |

57 | # |

58 | # SOS constraints must come after Bounds, General, Binary, and |

59 | # Semi-Continuous sections |

60 | # |

61 | self._output_continuous_variables = True |

62 | self._output_integer_variables = True |

63 | self._output_binary_variables = True |

64 | |

65 | # do I pre-pend the model name to all identifiers I output? |

66 | # useful in cases where you are writing components of multiple |

67 | # models to a single output LP file. |

68 | self._output_prefixes = False |

69 | |

70 | |

71 | def __call__(self, model, filename): |

72 | |

73 | if filename is None: |

74 | filename = model.name + ".lp" |

75 | OUTPUT=open(filename,"w") |

76 | symbol_map = self._print_model_LP(model,OUTPUT) |

77 | OUTPUT.close() |

78 | return filename, symbol_map |

79 | |

80 | |

81 | def _get_bound(self, exp): |

82 | |

83 | if isinstance(exp,expr._IdentityExpression): |

84 | return self._get_bound(exp._args[0]) |

85 | elif exp.is_constant(): |

86 | return exp() |

87 | else: |

88 | raise ValueError, "ERROR: nonconstant bound: " + str(exp) |

89 | return None |

90 | |

91 | |

92 | @staticmethod |

93 | def _collect_key ( a ): |

94 | |

95 | return a[0] |

96 | |

97 | |

98 | @staticmethod |

99 | def _no_label_error ( var ): |

100 | |

101 | msg = "Unable to find label for variable '%s'.\n" \ |

102 | 'Possibly uninstantiated model. Do any constraint or objective ' \ |

103 | 'rules reference the original model object, as opposed to the ' \ |

104 | 'passed model object? Alternatively, if you are developing code, ' \ |

105 | 'has the model instance been pre-processed?' |

106 | |

107 | raise ValueError, msg % str(var) |

108 | |

109 | |

110 | def _print_expr_linear(self, x, OUTPUT, **kwargs): |

111 | |

112 | """ |

113 | Return a expression as a string in LP format. |

114 | |

115 | Note that this function does not handle any differences in LP format |

116 | interpretation by the solvers (e.g. CPlex vs GLPK). That decision is |

117 | left up to the caller. |

118 | |

119 | required arguments: |

120 | x: A Pyomo linear encoding of an expression to write in LP format |

121 | |

122 | optional keyword arguments: |

123 | is_objective: set True if printing a quadratic objective. Required |

124 | for CPlex LP quadratic handling differences between objectives |

125 | and constraints. (boolean) |

126 | |

127 | print_offset: print expression offset [deprecated] (boolean) |

128 | """ |

129 | |

130 | is_objective = kwargs.pop('is_objective', False) |

131 | print_offset = kwargs.pop('print_offset', False) |

132 | |

133 | constant_term = x[0] |

134 | linear_terms = x[1] |

135 | |

136 | name_to_coefficient_map = {} |

137 | |

138 | for coefficient, var_value in linear_terms: |

139 | |

140 | if var_value.fixed is True: |

141 | |

142 | constant_term += (coefficient * var_value.value) |

143 | |

144 | else: |

145 | |

146 | prefix = "" |

147 | if self._output_prefixes is True: |

148 | parent_var = var_value.var |

149 | prefix = convert_name(parent_var.model.name) + "_" |

150 | |

151 | if not var_value.label: |

152 | self._no_label_error(var_value) |

153 | |

154 | name = prefix + var_value.label |

155 | |

156 | # due to potential disabling of expression simplification, |

157 | # variables might appear more than once - condense coefficients. |

158 | name_to_coefficient_map[name] = coefficient + name_to_coefficient_map.get(name,0.0) |

159 | |

160 | sorted_names = sorted(name_to_coefficient_map.keys()) |

161 | |

162 | for name in sorted_names: |

163 | |

164 | coefficient = name_to_coefficient_map[name] |

165 | |

166 | sign = '+' |

167 | if coefficient < 0: sign = '-' |

168 | print >>OUTPUT, '%s%f %s' % (sign, math.fabs(coefficient), name) |

169 | |

170 | if print_offset and (constant_term != 0.0): |

171 | sign = '+' |

172 | if constant_term < 0: sign = '-' |

173 | print >>OUTPUT, '%s%f %s' % (sign, math.fabs(constant_term), 'ONE_VAR_CONSTANT') |

174 | |

175 | return constant_term |

176 | |

177 | |

178 | def _print_expr_canonical(self, x, OUTPUT, **kwargs): |

179 | |

180 | """ |

181 | Return a expression as a string in LP format. |

182 | |

183 | Note that this function does not handle any differences in LP format |

184 | interpretation by the solvers (e.g. CPlex vs GLPK). That decision is |

185 | left up to the caller. |

186 | |

187 | required arguments: |

188 | x: A Pyomo canonical expression to write in LP format |

189 | |

190 | optional keyword arguments: |

191 | is_objective: set True if printing a quadratic objective. Required |

192 | for CPlex LP quadratic handling differences between objectives |

193 | and constraints. (boolean) |

194 | |

195 | print_offset: print expression offset [deprecated] (boolean) |

196 | """ |

197 | |

198 | is_objective = kwargs.pop('is_objective', False) |

199 | print_offset = kwargs.pop('print_offset', False) |

200 | |

201 | # |

202 | # Linear |

203 | # |

204 | if 1 in x: |

205 | |

206 | name_to_coefficient_map = {} |

207 | |

208 | for this_hash in x[1].iterkeys(): |

209 | |

210 | var_value = x[-1][this_hash.keys()[0]] |

211 | coefficient = x[1][this_hash] |

212 | |

213 | prefix = "" |

214 | |

215 | if self._output_prefixes is True: |

216 | parent_var = var_value.var |

217 | prefix = convert_name(parent_var.model.name) + "_" |

218 | |

219 | label = var_value.label |

220 | if not label: |

221 | self._no_label_error(var_value) |

222 | |

223 | name = prefix + label |

224 | |

225 | name_to_coefficient_map[name] = coefficient |

226 | |

227 | sorted_names = sorted(name_to_coefficient_map.keys()) |

228 | |

229 | for name in sorted_names: |

230 | coef = name_to_coefficient_map[name] |

231 | sign = '+' |

232 | if coef < 0: sign = '-' |

233 | print >>OUTPUT, '%s%f %s' % (sign, math.fabs(coef), name) |

234 | |

235 | # |

236 | # Quadratic |

237 | # |

238 | if 2 in x: |

239 | |

240 | print >>OUTPUT, "+ [" |

241 | |

242 | for id in sorted(x[2].keys()): |

243 | |

244 | coefficient = x[2][id] |

245 | sign = '+' |

246 | if coefficient < 0: |

247 | sign = '-' |

248 | coefficient = math.fabs(coefficient) |

249 | |

250 | if is_objective: |

251 | coefficient *= 2 |

252 | # times 2 because LP format requires /2 for all the quadratic |

253 | # terms /of the objective only/. Discovered the last bit thru |

254 | # trial and error. Obnoxious. |

255 | # Ref: ILog CPlex 8.0 User's Manual, p197. |

256 | |

257 | print >>OUTPUT, sign, coefficient, |

258 | |

259 | term_variables = [] |

260 | |

261 | for var in id: |

262 | |

263 | var_value = x[-1][var] |

264 | |

265 | label = var_value.label |

266 | if not label: |

267 | self._no_label_error(var_value) |

268 | |

269 | prefix = "" |

270 | |

271 | if self._output_prefixes is True: |

272 | parent_var = var_value.var |

273 | prefix = convert_name(parent_var.model.name) + "_" |

274 | |

275 | name = prefix + label |

276 | term_variables.append(name) |

277 | |

278 | if len(term_variables) == 2: |

279 | print >>OUTPUT, term_variables[0],"*",term_variables[1], |

280 | else: |

281 | print >>OUTPUT, term_variables[0],"^ 2", |

282 | |

283 | print >>OUTPUT, "" |

284 | |

285 | print >>OUTPUT, "]", |

286 | if is_objective: |

287 | print >>OUTPUT, ' / 2' |

288 | # divide by 2 because LP format requires /2 for all the quadratic |

289 | # terms. Weird. Ref: ILog CPlex 8.0 User's Manual, p197 |

290 | else: |

291 | print >>OUTPUT, "" |

292 | |

293 | |

294 | # |

295 | # Constant offset |

296 | # |

297 | offset=0.0 |

298 | if 0 in x: |

299 | offset = x[0][None] |

300 | if print_offset and offset != 0.0: |

301 | sign = '+' |

302 | if offset < 0: sign = '-' |

303 | print >>OUTPUT, '%s%f %s' % (sign, math.fabs(offset), 'ONE_VAR_CONSTANT') |

304 | |

305 | # |

306 | # Return constant offset |

307 | # |

308 | return offset |

309 | |

310 | # @deprecated |

311 | def _print_quadterm_arg(self, arg, is_minimizing, OUTPUT): |

312 | |

313 | if isinstance(arg,expr._ProductExpression): |

314 | # NOTE: We need to handle quadratics defined with the 'pow' |

315 | # term here. |

316 | # WARNING: The following code is very specific to progressive |

317 | # hedging, with a very specific format assumed. We do need to |

318 | # handle more general expressions, but we'll worry about that |

319 | # at a latter time. |

320 | blend = arg._numerator[0]() |

321 | |

322 | if blend is 1: |

323 | |

324 | rho = arg._numerator[1]() |

325 | |

326 | pow_expression = arg._numerator[2] |

327 | |

328 | base = pow_expression._args[0] |

329 | exponent = pow_expression._args[1] |

330 | |

331 | if not isinstance(base,expr._SumExpression): |

332 | msg = 'Quadratic term base must be a _SumExpression' |

333 | raise ValueError, msg |

334 | |

335 | if not isinstance(exponent,NumericConstant): |

336 | msg = 'Quadratic term exponent must be a NumericConstant' |

337 | raise ValueError, msg |

338 | |

339 | variable = base._args[0] |

340 | offset = base._args[1] |

341 | if variable.status is not VarStatus.unused: |

342 | |

343 | sign = '-' |

344 | if is_minimizing is True: |

345 | sign = '+' |

346 | print >>OUTPUT, '%s [ %s %s^2 ] / 2' % ( |

347 | sign, |

348 | str(rho), |

349 | variable.label |

350 | ) |

351 | |

352 | sign = '-' |

353 | if (is_minimizing is True) == (offset.value < 0): |

354 | sign = '+' |

355 | print >>OUTPUT, '%s %s %s' % ( |

356 | sign, |

357 | str(abs(rho*offset.value)), |

358 | variable.label |

359 | ) |

360 | |

361 | objective_offset = (rho * offset.value*offset.value /2.0) |

362 | fmt = ' -%s ONE_VAR_CONSTANT' |

363 | if is_minimizing is True: fmt = ' +%s ONE_VAR_CONSTANT' |

364 | print >>OUTPUT, fmt % str(objective_offset) |

365 | |

366 | elif isinstance(arg,NumericConstant): |

367 | # this is the "0.0" element that forms the initial expression |

368 | # -- the quadratic sub-expressions aren't known to the presolve |

369 | # routines. ideally unnecessary - hacked in for now. |

370 | pass |

371 | |

372 | else: |

373 | msg = '%s\nUnknown expression sub-type found in quadratic ' \ |

374 | 'objective expression' |

375 | raise ValueError, msg % `arg` |

376 | |

377 | |

378 | # @deprecated |

379 | def _print_quadterm(self, x, is_minimizing, OUTPUT): |

380 | |

381 | # The LP format doesn't allow for expression of constant terms in the |

382 | # objective. A work-around involves tracking the sum of constant terms |

383 | # in the quadratic terms, and then writing that out with a dummy |

384 | # variable forced equal to one. |

385 | print >>OUTPUT, "" |

386 | if isinstance(x, expr._SumExpression): # multiple terms means a sum expression |

387 | for arg in x._args: |

388 | self._print_quadterm_arg(arg, is_minimizing, OUTPUT) |

389 | elif isinstance(x, expr._ProductExpression): # if only a single term is involved, you have a product expression |

390 | self._print_quadterm_arg(x, is_minimizing, OUTPUT) |

391 | else: |

392 | msg = 'Unknown expression of type=%s encountered when printing quaratic term expression' |

393 | raise ValueError, msg % str(type(x)) |

394 | |

395 | @staticmethod |

396 | def printSOS(con, name, OUTPUT, index=None): |

397 | |

398 | """ |

399 | Returns the SOS constraint (as a string) associated with con. |

400 | If specified, index is passed to con.sos_set(). |

401 | |

402 | Arguments: |

403 | con The SOS constraint object |

404 | name The name of the variable |

405 | OUTPUT The output stream |

406 | index [Optional] the index to pass to the sets indexing the variables. |

407 | """ |

408 | |

409 | # The name of the variable being indexed |

410 | varName = str(con.sos_vars()) |

411 | |

412 | # The list of variable names to be printed, including indices |

413 | varNames = [] |

414 | |

415 | # Get all the variables |

416 | if index is None: |

417 | tmpSet = con.sos_set() |

418 | else: |

419 | tmpSet = con.sos_set()[index] |

420 | for x in tmpSet: |

421 | strX = str(x) |

422 | if strX[0] == "(": |

423 | # its a tuple, remove whitespace |

424 | varNames.append(varName + strX.replace(" ","")) |

425 | else: |

426 | # its a single number, add parenthesis |

427 | varNames.append(varName + "(" + strX + ")") |

428 | |

429 | conNameIndex = "" |

430 | if index is not None: |

431 | conNameIndex = str(index) |

432 | |

433 | print >>OUTPUT, '%s%s: S%s::' % (name, conNameIndex, con.sos_level()) |

434 | |

435 | # We need to 'weight' each variable |

436 | # For now we just increment a counter |

437 | for i in range(0, len(varNames)): |

438 | print >>OUTPUT, '%s:%f' % (varNames[i], i+1) |

439 | |

440 | def _print_model_LP(self, model, OUTPUT): |

441 | |

442 | symbol_map = {} |

443 | _obj = model.active_components(Objective) |

444 | |

445 | supports_quadratic = model.has_capability('quadratic') |

446 | |

447 | # |

448 | # Objective |

449 | # |

450 | if self._output_objectives is True: |

451 | |

452 | printed_quadterm = False |

453 | if len(_obj) == 0: |

454 | msg = "ERROR: No objectives defined for input model '%s'; " \ |

455 | ' cannot write legal LP file' |

456 | raise ValueError, msg % str( model.name ) |

457 | |

458 | if _obj[ _obj.keys()[0] ].sense == maximize: |

459 | print >>OUTPUT, "max " |

460 | else: |

461 | print >>OUTPUT, "min " |

462 | |

463 | obj = _obj[ _obj.keys()[0] ] |

464 | obj_keys = obj.keys() |

465 | if len(obj_keys) > 1: |

466 | keys = obj.name + ' (indexed by: %s)' % ', '.join(map(str, obj_keys)) |

467 | |

468 | msg = "More than one objective defined for input model '%s'; " \ |

469 | 'Cannot write legal LP file\n' \ |

470 | 'Objectives: %s' |

471 | raise ValueError, msg % ( str(model.name), keys ) |

472 | |

473 | for key in obj_keys: |

474 | |

475 | if obj[key].repn is None: |

476 | raise RuntimeError, "No canonical representation identified for objective="+obj[key].name |

477 | |

478 | if is_constant(obj[key].repn): |

479 | |

480 | print ("Warning: Constant objective detected, replacing " + |

481 | "with a placeholder to prevent solver failure.") |

482 | |

483 | print >>OUTPUT, obj._data[None].label+": +0.0 ONE_VAR_CONSTANT" |

484 | |

485 | # Skip the remaining logic of the section |

486 | continue |

487 | |

488 | if is_quadratic( obj[key].repn ): |

489 | if not supports_quadratic: |

490 | msg = 'Solver unable to handle quadratic ' \ |

491 | "objective expressions. Objective at issue: %s%s." |

492 | if key is None: |

493 | msg %= (obj.name, " ") |

494 | else: |

495 | msg %= (obj.name, '[%s]' % key ) |

496 | raise ValueError, msg |

497 | |

498 | elif is_nonlinear( obj[key].repn ): |

499 | msg = "Cannot write legal LP file. Objective '%s%s' " \ |

500 | 'has nonlinear terms that are not quadratic.' |

501 | if key is None: msg %= (obj.name, '') |

502 | else: msg %= (obj.name, '[%s]' % key ) |

503 | raise ValueError, msg |

504 | |

505 | symbol_map['__default_objective__'] = obj._data[None].label |

506 | print >>OUTPUT, obj._data[None].label+':' |

507 | |

508 | offset = self._print_expr_canonical(obj[key].repn, |

509 | OUTPUT, |

510 | print_offset=True, |

511 | is_objective=True) |

512 | |

513 | if obj._quad_subexpr is not None: |

514 | self._print_quadterm(obj._quad_subexpr, (_obj[ _obj.keys()[0] ].sense == minimize), OUTPUT) |

515 | printed_quadterm = True |

516 | |

517 | print >>OUTPUT, "" |

518 | |

519 | # Constraints |

520 | # |

521 | # If there are no non-trivial constraints, you'll end up with an empty |

522 | # constraint block. CPLEX is OK with this, but GLPK isn't. And |

523 | # eliminating the constraint block (i.e., the "s.t." line) causes GLPK |

524 | # to whine elsewhere. output a warning if the constraint block is empty, |

525 | # so users can quickly determine the cause of the solve failure. |

526 | |

527 | if self._output_constraints is True: |

528 | |

529 | # for now, if this routine isn't writing everything, then assume a |

530 | # meta-level handler is dealing with the writing of the transitional |

531 | # elements - these should probably be done in the form of |

532 | # "end_objective()" and "end_constraint()" helper methods. |

533 | if self._output_objectives == self._output_variables == True: |

534 | print >>OUTPUT, "s.t." |

535 | print >>OUTPUT, "" |

536 | |

537 | active_constraints = model.active_components(Constraint) |

538 | active_constraintlists = model.active_components(ConstraintList) |

539 | have_nontrivial = False |

540 | for constraint in itertools.chain(active_constraints.itervalues(), active_constraintlists.itervalues()): |

541 | if constraint.trivial: |

542 | continue |

543 | |

544 | have_nontrivial=True |

545 | |

546 | for index in sorted(constraint.keys()): |

547 | |

548 | constraint_data = constraint[index] |

549 | if not constraint_data.active: |

550 | continue |

551 | |

552 | # if expression trees have been linearized, then the canonical |

553 | # representation attribute on the constraint data object will |

554 | # be equal to None. |

555 | if (constraint_data.repn is not None): |

556 | |

557 | # There are conditions, e.g., when fixing variables, under which |

558 | # a constraint block might be empty. Ignore these, for both |

559 | # practical reasons and the fact that the CPLEX LP format |

560 | # requires a variable in the constraint body. It is also |

561 | # possible that the body of the constraint consists of only a |

562 | # constant, in which case the "variable" of |

563 | if is_constant(constraint_data.repn): |

564 | # this happens *all* the time in many applications, |

565 | # including PH - so suppress the warning. |

566 | # |

567 | #msg = 'WARNING: ignoring constraint %s[%s] which is ' \ |

568 | # 'constant' |

569 | #print msg % (str(C),str(index)) |

570 | continue |

571 | |

572 | if is_quadratic( constraint_data.repn ): |

573 | if not supports_quadratic: |

574 | msg = 'Solver unable to handle quadratic expressions.'\ |

575 | " Constraint at issue: '%s%%s'" |

576 | msg %= constraint.name |

577 | if index is None: msg %= '' |

578 | else: msg %= '[%s]' % index |

579 | |

580 | raise ValueError, msg |

581 | |

582 | elif is_nonlinear( constraint_data.repn ): |

583 | msg = "Cannot write legal LP file. Constraint '%s%s' " \ |

584 | 'has a body with nonlinear terms.' |

585 | if index is None: |

586 | msg %= ( constraint.name, '') |

587 | else: |

588 | msg %= ( constraint.name, '[%s]' % index ) |

589 | raise ValueError, msg |

590 | |

591 | prefix = "" |

592 | if self._output_prefixes is True: |

593 | if constraint.model is None: |

594 | msg = "Constraint '%s' has no model attribute - no " \ |

595 | 'label prefix can be assigned' |

596 | raise RuntimeError, msg % constraint_data.label |

597 | prefix = constraint.model.name+"_" |

598 | |

599 | con_name = constraint_data.label |

600 | |

601 | if constraint_data._equality: |

602 | label = '%sc_e_%s_' % (prefix, con_name) |

603 | symbol_map['%sc_e_%s_' % (prefix, constraint_data.label)] = constraint_data.label |

604 | print >>OUTPUT, label+':' |

605 | if constraint_data.lin_body is not None: |

606 | offset = self._print_expr_linear(constraint_data.lin_body, OUTPUT) |

607 | else: |

608 | offset = self._print_expr_canonical(constraint_data.repn, OUTPUT) |

609 | bound = constraint_data.lower |

610 | bound = str(self._get_bound(bound) - offset) |

611 | print >>OUTPUT, "=", bound |

612 | print >>OUTPUT, "" |

613 | else: |

614 | # TBD: ENCAPSULATE THE IF-ELSE INTO A SINGLE UTILITY METHOD |

615 | # TBD: MAKE THE _data and C[ndx] calls consistent - everything should just reference the data directly |

616 | if constraint_data.lower is not None: |

617 | label = '%sc_l_%s_' % (prefix, con_name) |

618 | symbol_map['%sc_l_%s_' % (prefix, constraint_data.label)] = constraint_data.label |

619 | print >>OUTPUT, label+':' |

620 | if constraint_data.lin_body is not None: |

621 | offset = self._print_expr_linear(constraint_data.lin_body, OUTPUT) |

622 | else: |

623 | offset = self._print_expr_canonical(constraint_data.repn, OUTPUT) |

624 | bound = constraint_data.lower |

625 | bound = str(self._get_bound(bound) - offset) |

626 | print >>OUTPUT, ">=", bound |

627 | print >>OUTPUT, "" |

628 | if constraint_data.upper is not None: |

629 | label = '%sc_u_%s_' % (prefix, con_name) |

630 | symbol_map['%sc_u_%s_' % (prefix, constraint_data.label)] = constraint_data.label |

631 | print >>OUTPUT, label+':' |

632 | if constraint_data.lin_body is not None: |

633 | offset = self._print_expr_linear(constraint_data.lin_body, OUTPUT) |

634 | else: |

635 | offset = self._print_expr_canonical(constraint_data.repn, OUTPUT) |

636 | bound = constraint_data.upper |

637 | bound = str(self._get_bound(bound) - offset) |

638 | print >>OUTPUT, "<=", bound |

639 | print >>OUTPUT, "" |

640 | |

641 | if not have_nontrivial: |

642 | print 'WARNING: Empty constraint block written in LP format ' \ |

643 | '- solver may error' |

644 | |

645 | # the CPLEX LP format doesn't allow constants in the objective (or |

646 | # constraint body), which is a bit silly. To avoid painful |

647 | # book-keeping, we introduce the following "variable", constrained |

648 | # to the value 1. This is used when quadratic terms are present. |

649 | # worst-case, if not used, is that CPLEX easily pre-processes it out. |

650 | prefix = "" |

651 | if self._output_prefixes is True: |

652 | prefix = model.name + "_" |

653 | print >>OUTPUT, '%sc_e_ONE_VAR_CONSTANT: ' % prefix |

654 | print >>OUTPUT, '%sONE_VAR_CONSTANT = 1.0' % prefix |

655 | print >>OUTPUT, "" |

656 | |

657 | # |

658 | # Bounds |

659 | # |

660 | |

661 | if self._output_variables is True: |

662 | |

663 | # For now, if this routine isn't writing everything, then assume a |

664 | # meta-level handler is dealing with the writing of the transitional |

665 | # elements - these should probably be done in the form of |

666 | # "end_objective()" and "end_constraint()" helper methods. |

667 | if True == self._output_objectives == self._output_constraints: |

668 | print >>OUTPUT, "bounds " |

669 | |

670 | # Scan all variables even if we're only writing a subset of them. |

671 | # required because we don't store maps by variable type currently. |

672 | |

673 | # Track the number of integer and binary variables, so you can |

674 | # output their status later. |

675 | niv = nbv = 0 |

676 | |

677 | active_variables = model.active_components(Var) |

678 | |

679 | for variable_name in sorted(active_variables.keys()): |

680 | |

681 | var = active_variables[variable_name] |

682 | |

683 | for index, var_value in var.iteritems(): |

684 | if isinstance(var_value.domain, IntegerSet): niv += 1 |

685 | elif isinstance(var_value.domain, BooleanSet): nbv += 1 |

686 | |

687 | if self._output_continuous_variables is True: |

688 | |

689 | for index in sorted(var._varval.keys()): |

690 | |

691 | if not var._varval[index].active: |

692 | continue |

693 | prefix = "" |

694 | if self._output_prefixes is True: |

695 | prefix = convert_name(var.model.name)+"_" |

696 | |

697 | # if the variable isn't referenced in the model, don't |

698 | # output bounds... |

699 | if var[index].id != -1: |

700 | # in the CPLEX LP file format, the default variable |

701 | # bounds are 0 and +inf. These bounds are in |

702 | # conflict with Pyomo, which assumes -inf and inf |

703 | # (which we would argue is more rational). |

704 | print >>OUTPUT," ", |

705 | if var[index].lb is not None: |

706 | print >>OUTPUT, str(value(var[index].lb())), "<= ", |

707 | else: |

708 | print >>OUTPUT, " -inf <= ", |

709 | name_to_output = prefix + var[index].label |

710 | if name_to_output == "e": |

711 | msg = 'Attempting to write variable with name' \ |

712 | "'e' in a CPLEX LP formatted file - " \ |

713 | 'will cause a parse failure due to ' \ |

714 | 'confusion with numeric values ' \ |

715 | 'expressed in scientific notation' |

716 | raise ValueError, msg |

717 | print >>OUTPUT, name_to_output, |

718 | if var[index].ub is not None: |

719 | print >>OUTPUT, " <=", str(value(var[index].ub())) |

720 | else: |

721 | print >>OUTPUT, " <= +inf" |

722 | |

723 | if (niv > 0) and (self._output_integer_variables is True): |

724 | |

725 | # If we're outputting the whole model, then assume we can output |

726 | # the "general" header. If not, then assume a meta-level process |

727 | # is taking care of it. |

728 | if True == self._output_objectives == self._output_constraints: |

729 | print >>OUTPUT, "general" |

730 | |

731 | prefix = "" |

732 | if self._output_prefixes is True: |

733 | prefix = convert_name(var.model.name)+"_" |

734 | |

735 | for variable_name in sorted(active_variables.keys()): |

736 | |

737 | var = active_variables[variable_name] |

738 | |

739 | for index in sorted(var.integer_keys()): |

740 | |

741 | if not var[index].active: |

742 | continue |

743 | if var[index].id != -1: # skip if variable not referenced |

744 | var_name = prefix + var[index].label |

745 | print >>OUTPUT, ' ', var_name |

746 | |

747 | if (nbv > 0) and (self._output_binary_variables is True): |

748 | |

749 | # If we're outputting the whole model, then assume we can output |

750 | # the "binary" header. if not, then assume a meta-level process |

751 | # is taking care of it. |

752 | if True == self._output_objectives == self._output_constraints: |

753 | print >>OUTPUT, "binary" |

754 | |

755 | prefix = "" |

756 | if self._output_prefixes is True: |

757 | prefix = convert_name(var.model.name)+"_" |

758 | |

759 | for variable_name in sorted(active_variables.keys()): |

760 | |

761 | var = active_variables[variable_name] |

762 | |

763 | for index in sorted(var.binary_keys()): |

764 | if not var[index].active: |

765 | continue |

766 | if var[index].id != -1: # skip if variable not referenced |

767 | var_name = prefix + var[index].label |

768 | print >>OUTPUT, ' ', var_name |

769 | |

770 | |

771 | # SOS constraints |

772 | # |

773 | # For now, we write out SOS1 and SOS2 constraints in the cplex format |

774 | # |

775 | # All Component objects are stored in model._component, which is a |

776 | # dictionary of {class: {objName: object}}. |

777 | # |

778 | # Consider the variable X, |

779 | # |

780 | # model.X = Var(...) |

781 | # |

782 | # We print X to CPLEX format as X(i,j,k,...) where i, j, k, ... are the |

783 | # indices of X. |

784 | # |

785 | # TODO: Allow users to specify the variables coefficients for custom |

786 | # branching/set orders |

787 | sosn = model.has_capability("sosn") |

788 | sos1 = model.has_capability("sos1") |

789 | sos2 = model.has_capability("sos2") |

790 | |

791 | if sosn or sos1 or sos2: |

792 | writtenSOS = False |

793 | constrs = model.components[SOSConstraint] |

794 | for name in constrs: |

795 | con = constrs[name] |

796 | level = con.sos_level() |

797 | if (level == 1 and sos1) or (level == 2 and sos2) or (sosn): |

798 | if writtenSOS == False: |

799 | print >>OUTPUT, "SOS" |

800 | writtenSOS = True |

801 | masterIndex = con.sos_set_set() |

802 | if None in masterIndex: |

803 | # A single constraint |

804 | self.printSOS(con, name, OUTPUT) |

805 | else: |

806 | # A series of indexed constraints |

807 | for index in masterIndex: |

808 | self.printSOS(con, name, OUTPUT, index) |

809 | |

810 | # |

811 | # wrap-up |

812 | # |

813 | if self._output_objectives == self._output_constraints == True: |

814 | # |

815 | # End |

816 | # |

817 | print >>OUTPUT, "end " |

818 | # |

819 | return symbol_map |

**Note:**See TracBrowser for help on using the repository browser.