#
source:
coopr.pyomo/trunk/coopr/pyomo/base/constraint.py
@
4331

Revision 4331, 35.8 KB checked in by tekl, 3 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 | __all__ = ['Constraint', 'SOSConstraint', 'ConstraintBase', 'ConstraintList', 'simple_constraint_rule', 'simple_constraintlist_rule'] |

12 | |

13 | import sys |

14 | import logging |

15 | import weakref |

16 | |

17 | from expr import * |

18 | from indexed_component import IndexedComponent |

19 | from misc import create_name, apply_indexed_rule |

20 | from numtypes import * |

21 | from numvalue import * |

22 | from label import * |

23 | from pyutilib.component.core import alias |

24 | from sets import _BaseSet, _SetContainer, _ProductSet, Set |

25 | from var import Var, _VarValue, _VarArray, _VarElement |

26 | from objective import Objective |

27 | import pyutilib.math |

28 | import pyutilib.misc |

29 | |

30 | from component import Component |

31 | from set_types import * |

32 | from sets import _SetContainer |

33 | |

34 | logger = logging.getLogger('coopr.pyomo') |

35 | |

36 | |

37 | def simple_constraint_rule( fn ): |

38 | """This is a decorator that translates None/True/False return values |

39 | into Constraint.Skip/Constraint.Feasible/Constraint.Infeasible. |

40 | This supports a simpler syntax in constraint rules, though these can be |

41 | more difficult to debug when errors occur. |

42 | |

43 | Example use: |

44 | |

45 | @simple_constraint_rule |

46 | def C_rule(model, i, j): |

47 | ... |

48 | """ |

49 | |

50 | def wrapper_function ( *args, **kwargs ): |

51 | value = fn( *args, **kwargs ) |

52 | if value is None: |

53 | return Constraint.Skip |

54 | elif value is True: |

55 | return Constraint.Feasible |

56 | elif value is False: |

57 | return Constraint.Infeasible |

58 | return value |

59 | return wrapper_function |

60 | |

61 | |

62 | def simple_constraintlist_rule( fn ): |

63 | """This is a decorator that translates None/True/False return values |

64 | into ConstraintList.End/Constraint.Feasible/Constraint.Infeasible. |

65 | This supports a simpler syntax in constraint rules, though these can be |

66 | more difficult to debug when errors occur. |

67 | |

68 | Example use: |

69 | |

70 | @simple_constraintlist_rule |

71 | def C_rule(model, i, j): |

72 | ... |

73 | """ |

74 | |

75 | def wrapper_function ( *args, **kwargs ): |

76 | value = fn( *args, **kwargs ) |

77 | if value is None: |

78 | return ConstraintList.End |

79 | elif value is True: |

80 | return Constraint.Feasible |

81 | elif value is False: |

82 | return Constraint.Infeasible |

83 | return value |

84 | return wrapper_function |

85 | |

86 | |

87 | |

88 | class ConstraintBase(IndexedComponent): |

89 | """ |

90 | Abstract base class for all constraint types. Keeps track of how many |

91 | constraint objects are in existence. |

92 | """ |

93 | |

94 | alias("ConstraintBase", "Abstract base class for all model constraints") |

95 | |

96 | # The number of explicit constraints declared. |

97 | _nExplicitConstraints = 0 |

98 | |

99 | def __init__(self, *args, **kwargs): |

100 | ctype = {'ctype': kwargs.pop("ctype", None)} |

101 | IndexedComponent.__init__(self, *args, **ctype) |

102 | |

103 | tmpname = kwargs.pop('name', 'unknown') |

104 | self.doc = kwargs.pop('doc', None ) |

105 | |

106 | # Increment the class count |

107 | ConstraintBase._inc_count() |

108 | |

109 | self._data = {} |

110 | |

111 | def dim(self): |

112 | return self._ndim |

113 | |

114 | def __len__(self): |

115 | return len(self._data.keys()) |

116 | |

117 | def keys(self): |

118 | return self._data.keys() |

119 | #return self._index |

120 | |

121 | def __contains__(self,ndx): |

122 | return ndx in self._data |

123 | #return ndx in self._index |

124 | |

125 | def __getitem__(self, ndx): |

126 | """This method returns a ConstraintData object. This object can be |

127 | coerced to a numeric value using the value() function, or using |

128 | explicity coercion with float(). |

129 | """ |

130 | if ndx in self._data: |

131 | return self._data[ndx] |

132 | msg = "Unknown index in constraint '%s': %s" |

133 | raise KeyError, msg % ( self.name, str(ndx) ) |

134 | |

135 | def __iter__(self): |

136 | return self._data.keys().__iter__() |

137 | |

138 | def __del__(self): |

139 | """ Remove this constraint from the count """ |

140 | self._inc_count(-1) |

141 | for k in self._data.keys(): |

142 | del self._data[k] |

143 | |

144 | @classmethod |

145 | def _inc_count(cls, n=1): |

146 | """ Alter the constraint count """ |

147 | cls._nExplicitConstraints += n |

148 | |

149 | @classmethod |

150 | def num_explicit_constraints(cls): |

151 | return cls._nExplicitConstraints |

152 | |

153 | |

154 | class ConstraintData(NumericValue): |

155 | |

156 | __slots__ = ('con','label','id','active','_equality','lower', |

157 | 'lower_ineq_strict','lin_body','body','upper', |

158 | 'upper_ineq_strict','repn','ampl_repn','dual', 'index') |

159 | |

160 | def __del__(self): |

161 | if NumericValue is not None: |

162 | NumericValue.__del__(self) |

163 | del self.con |

164 | del self.value |

165 | |

166 | def __init__(self, name, con): |

167 | |

168 | # IMPT: The following three lines are equivalent to calling the |

169 | # basic NumericValue constructor, i.e., as follows: |

170 | # NumericValue.__init__(self,name,Reals,None,False) |

171 | # That particular constructor call takes a lot of time |

172 | # for big models, and is unnecessary because we're not |

173 | # validating any values. (Arguably, "domain" isn't even |

174 | # needed.) |

175 | self.name = name |

176 | self.domain = Reals |

177 | self.value = None |

178 | self.index = None |

179 | |

180 | # the parent constraint object. |

181 | self.con = weakref.ref(con) |

182 | |

183 | # the label is a modification of the input name, with certain symbols |

184 | # that are typically problematic for solver input files re-mapped (e.g., |

185 | # curly braces) to more friendly symbols. create a default label right |

186 | # away - it can be modified if a particular solver needs it. |

187 | # self.label = None (is set one way or another via the following method) |

188 | self.sync_label() |

189 | |

190 | # TBD: Document where this gets populated. |

191 | self.id = None |

192 | |

193 | # TBD: Mirroring the variable class, the constraint data class should really "know" its index and its parent constraint. |

194 | |

195 | # is this variable an active component of the current model? |

196 | self.active = True |

197 | |

198 | self._equality = False |

199 | self.lower = None |

200 | self.lower_ineq_strict = None |

201 | self.lin_body = None # a linear compilation of the source expressions tree. |

202 | self.body = None # an expression tree encoding of the constraint body. if None, an alternative representation (e.g., lin_body) will be != None. |

203 | self.upper = None |

204 | self.upper_ineq_strict = None |

205 | |

206 | self.repn = None |

207 | self.ampl_repn = None |

208 | |

209 | self.dual = None |

210 | |

211 | def __getstate__(self): |

212 | result = NumericValue.__getstate__(self) |

213 | for i in ConstraintData.__slots__: |

214 | result[i] = getattr(self, i) |

215 | return result |

216 | |

217 | def sync_label(self): |

218 | my_name = self.name |

219 | if my_name is not None: |

220 | self.label = label_from_name(my_name) |

221 | parent_constraint = self.con() |

222 | if (parent_constraint is not None) and (parent_constraint.model is not None): |

223 | parent_constraint.model()._label_constraint_map[self.label] = self |

224 | else: |

225 | self.label = None |

226 | |

227 | def __call__(self, exception=True): |

228 | if self.body is None: |

229 | return None |

230 | return self.body() |

231 | |

232 | def activate(self): |

233 | self.active=True |

234 | |

235 | def deactivate(self): |

236 | self.active=False |

237 | |

238 | |

239 | class Constraint(ConstraintBase, NumericValue): |

240 | """An object that defines a objective expression""" |

241 | |

242 | alias("Constraint", "Constraint expressions in a model.") |

243 | |

244 | NoConstraint = (1000,) |

245 | Skip = (1000,) |

246 | Infeasible = (1001,) |

247 | Violated = (1001,) |

248 | Feasible = (1002,) |

249 | Satisfied = (1002,) |

250 | |

251 | def __del__(self): |

252 | if ConstraintBase is not None: |

253 | ConstraintBase.__del__(self) |

254 | if NumericValue is not None: |

255 | NumericValue.__del__(self) |

256 | if 'rule' in self.__dict__.keys() and self.rule is not None: |

257 | del self.rule |

258 | |

259 | def __init__(self, *args, **kwargs): |

260 | """ |

261 | Construct an objective expression with rule to construct the |

262 | expression |

263 | |

264 | keyword arguments: |

265 | name: name of this object |

266 | rule: function or rule definition of constraint |

267 | expr: same as rule |

268 | doc: documentation string for constraint |

269 | """ |

270 | # See if 'ctype' was passed as a keyword argument; |

271 | # this allows derived classses to alert Pyomo to |

272 | # their existence through super() calls. If not, |

273 | # pass Constraint |

274 | tkwd = {'ctype': kwargs.pop('ctype', Constraint)} |

275 | |

276 | # Pass some arguments to ConstraintBase |

277 | tkwd['doc'] = kwargs.pop('doc', None) |

278 | tkwd['name'] = kwargs.get('name', 'unknown') |

279 | |

280 | ConstraintBase.__init__(self, *args, **tkwd) |

281 | |

282 | tmpname = kwargs.pop('name', 'unknown') |

283 | tmprule = kwargs.pop('rule', None ) |

284 | tmprule = kwargs.pop('expr', tmprule ) |

285 | |

286 | self._no_rule_init = kwargs.pop('noruleinit', None ) |

287 | |

288 | if ( kwargs ): # if dict not empty, there's an error. Let user know. |

289 | msg = "Creating constraint '%s': unknown option(s)\n\t%s" |

290 | msg = msg % ( tmpname, ', '.join(kwargs.keys()) ) |

291 | raise ValueError, msg |

292 | |

293 | # _no_rule_init is a flag specified by the user to indicate that no |

294 | # construction rule will be specified, and that constraints will be |

295 | # explicitly added by the user. set via the "noruleinit" keyword. value |

296 | # doesn't matter, as long as it isn't "None". |

297 | |

298 | self._data = {} |

299 | |

300 | NumericValue.__init__(self,tmpname,None,None,True) |

301 | |

302 | if None in self._data: |

303 | # As far as I can tell, this never executes? 2010 Jun 08 |

304 | self._data[None].name=tmpname |

305 | self.rule = tmprule |

306 | self.trivial = False # True if all constraint indicies have trivial expressions. |

307 | |

308 | def __getstate__(self): |

309 | result = NumericValue.__getstate__(self) |

310 | for key,value in self.__dict__.iteritems(): |

311 | result[key]=value |

312 | return result |

313 | |

314 | def clear(self): |

315 | self._data = {} |

316 | |

317 | def __call__(self, exception=True): |

318 | if len(self._data) == 0: |

319 | return None |

320 | if None in self._data: |

321 | if self._data[None].body is None: |

322 | return None |

323 | return value(self._data[None].body) |

324 | if exception: |

325 | msg = 'Cannot compute the value of an array of constraints' |

326 | raise ValueError, msg |

327 | |

328 | |

329 | def construct(self, data=None): |

330 | |

331 | # cache the debug generation flag, to avoid the expense of |

332 | # calling isEnabledFor() for each index - this is far too |

333 | # expensive an operation to perform deep in a loop. the only |

334 | # potential down-side is that the debug logging is either |

335 | # disabled or enabled globally for the duration of this |

336 | # method invocation - which doesn't seem like much of |

337 | # (if any) limitation in practice. |

338 | generate_debug_messages = (__debug__ is True) and (logger.isEnabledFor(logging.DEBUG) is True) |

339 | |

340 | if generate_debug_messages is True: |

341 | logger.debug("Constructing constraint %s",self.name) |

342 | |

343 | if (self._no_rule_init is not None) and (self.rule is not None): |

344 | logger.warn("noruleinit keyword is being used in conjunction with " |

345 | "rule keyword for constraint '%s'; defaulting to " |

346 | "rule-based construction.", self.name) |

347 | if self.rule is None: |

348 | if self._no_rule_init is None: |

349 | logger.warn("No construction rule or expression specified for " |

350 | "constraint '%s'", self.name) |

351 | return |

352 | if self._constructed: |

353 | return |

354 | self._constructed=True |

355 | |

356 | # |

357 | # Local variables for code optimization |

358 | # |

359 | _self_rule = self.rule |

360 | _self_model = self.model() |

361 | |

362 | # |

363 | if None in self._index: |

364 | if len(self._index) != 1: |

365 | raise IndexError, "Internal error: constructing constraint "\ |

366 | "with both None and Index set" |

367 | if generate_debug_messages: |

368 | logger.debug(" Constructing single constraint (index=None)") |

369 | if isinstance(_self_rule,Expression): |

370 | expr = _self_rule |

371 | else: |

372 | expr = _self_rule(_self_model) |

373 | self.add(None, expr) |

374 | else: |

375 | if isinstance(_self_rule,Expression): |

376 | raise IndexError, "Cannot define multiple indices in a " \ |

377 | "constraint with a single expression" |

378 | for index in self._index: |

379 | if generate_debug_messages: |

380 | logger.debug(" Constructing constraint index "+str(index)) |

381 | self.add( index, apply_indexed_rule( self, _self_rule, |

382 | _self_model, index ) ) |

383 | |

384 | def add(self, index, expr): |

385 | # index: the constraint index (should probably be renamed) |

386 | # expr: the constraint expression |

387 | |

388 | # Convert deprecated expression values |

389 | if expr is None: |

390 | logger.warning("DEPRECATION WARNING: Expression defined using None instead of Constraint.Skip - constraint="+create_name(self.name, index)) |

391 | expr = Constraint.Skip |

392 | if expr is True: |

393 | logger.warning("DEPRECATION WARNING: Expression defined using True instead of Constraint.Feasible - constraint="+create_name(self.name, index)) |

394 | expr = Constraint.Feasible |

395 | if expr is False: |

396 | logger.warning("DEPRECATION WARNING: Expression defined using False instead of Constraint.Infeasible - constraint="+create_name(self.name, index)) |

397 | expr = Constraint.Infeasible |

398 | # |

399 | expr_type = type(expr) |

400 | # |

401 | # Ignore an 'empty' constraint |

402 | # |

403 | if expr_type is tuple and len(expr) == 1: |

404 | if expr == Constraint.Skip or expr == Constraint.Feasible: |

405 | return |

406 | if expr == Constraint.Infeasible: |

407 | raise ValueError, "Constraint '%s' is always infeasible" % create_name(self.name,index) |

408 | # |

409 | # |

410 | # Local variables to optimize runtime performance |

411 | # |

412 | conData = ConstraintData(create_name(self.name,index), self) |

413 | conData.index = index |

414 | conData.lower_ineq_strict = conData.upper_ineq_strict = False |

415 | |

416 | if expr_type is tuple: # or expr_type is list: |

417 | # |

418 | # Form equality expression |

419 | # |

420 | if len(expr) == 2: |

421 | arg0 = expr[0] |

422 | if arg0 is not None: |

423 | arg0 = as_numeric(arg0) |

424 | arg1 = expr[1] |

425 | if arg1 is not None: |

426 | arg1 = as_numeric(arg1) |

427 | |

428 | conData._equality = True |

429 | if arg1 is None or arg1.is_constant(): |

430 | conData.lower = conData.upper = arg1 |

431 | conData.body = arg0 |

432 | elif arg0 is None or arg0.is_constant(): |

433 | conData.lower = conData.upper = arg0 |

434 | conData.body = arg1 |

435 | else: |

436 | conData.lower = conData.upper = NumericConstant(None,None,0) |

437 | conData.body = arg0 - arg1 |

438 | # |

439 | # Form inequality expression |

440 | # |

441 | elif len(expr) == 3: |

442 | arg0 = expr[0] |

443 | if arg0 is not None: |

444 | arg0 = as_numeric(arg0) |

445 | if not arg0.is_constant(): |

446 | msg = "Constraint '%s' found a 3-tuple (lower, " \ |

447 | "expression, upper) but the lower value was "\ |

448 | "non-constant" |

449 | raise ValueError, msg % (conData.name,) |

450 | |

451 | arg1 = expr[1] |

452 | if arg1 is not None: |

453 | arg1 = as_numeric(arg1) |

454 | |

455 | arg2 = expr[2] |

456 | if arg2 is not None: |

457 | arg2 = as_numeric(arg2) |

458 | if not arg2.is_constant(): |

459 | msg = "Constraint '%s' found a 3-tuple (lower, " \ |

460 | "expression, upper) but the upper value was "\ |

461 | "non-constant" |

462 | raise ValueError, msg % (conData.name,) |

463 | |

464 | conData.lower = arg0 |

465 | conData.body = arg1 |

466 | conData.upper = arg2 |

467 | else: |

468 | msg = "Constructor rule for constraint '%s' returned a tuple" \ |

469 | ' of length %d. Expecting a tuple of length 2 or 3:\n' \ |

470 | 'Equality: (left, right)\n' \ |

471 | 'Inequality: (lower, expression, upper)' |

472 | raise ValueError, msg % ( self.name, len(expr) ) |

473 | |

474 | relational_expr = False |

475 | else: |

476 | try: |

477 | relational_expr = expr.is_relational() |

478 | if not relational_expr: |

479 | msg = "Constraint '%s' does not have a proper value. " \ |

480 | "Found '%s'\nExpecting a tuple or equation. " \ |

481 | "Examples:\n" \ |

482 | " summation( model.costs ) == model.income\n" \ |

483 | " (0, model.price[ item ], 50)" |

484 | raise ValueError, msg % ( conData.name, str(expr) ) |

485 | except: |

486 | msg = "Constraint '%s' does not have a proper value. " \ |

487 | "Found '%s'\nExpecting a tuple or equation. " \ |

488 | "Examples:\n" \ |

489 | " summation( model.costs ) == model.income\n" \ |

490 | " (0, model.price[ item ], 50)" \ |

491 | % ( conData.name, str(expr) ) |

492 | if type(expr) is bool: |

493 | msg +=""" |

494 | Note: constant Boolean expressions are not valid constraint expressions. |

495 | Some apparently non-constant compound inequalities (e.g. "expr >= 0 <= 1") |

496 | can return boolean values; the proper form for compound inequalities is |

497 | always "lb <= expr <= ub".""" |

498 | raise ValueError, msg |

499 | |

500 | # |

501 | # Special check for chainedInequality errors like "if var < 1:" |

502 | # within rules. Catching them here allows us to provide the |

503 | # user with better (and more immediate) debugging information. |

504 | # We don't want to check earlier because we want to provide a |

505 | # specific debugging message if the construction rule returned |

506 | # True/False; for example, if the user did ( var < 1 > 0 ) |

507 | # (which also results in a non-None chainedInequality value) |

508 | # |

509 | if generate_relational_expression.chainedInequality is not None: |

510 | from expr import chainedInequalityErrorMessage |

511 | raise TypeError, chainedInequalityErrorMessage() |

512 | |

513 | # |

514 | # Process relational expressions (i.e. explicit '==', '<', and '<=') |

515 | # |

516 | if relational_expr: |

517 | if expr_type is _EqualityExpression: |

518 | # Equality expression: only 2 arguments! |

519 | conData._equality = True |

520 | if expr._args[1].is_constant(): |

521 | conData.lower = conData.upper = expr._args[1] |

522 | conData.body = expr._args[0] |

523 | elif expr._args[0].is_constant(): |

524 | conData.lower = conData.upper = expr._args[0] |

525 | conData.body = expr._args[1] |

526 | else: |

527 | conData.lower = conData.upper = NumericConstant(None,None,0) |

528 | conData.body = generate_expression('_','sub', expr._args[0], expr._args[1]) |

529 | else: |

530 | # Inequality expression: 2 or 3 arguments |

531 | if len(expr._args) == 3: |

532 | if not expr._args[0].is_constant(): |

533 | msg = "Constraint '%s' found a double-sided "\ |

534 | "inequality expression (lower <= expression "\ |

535 | "<= upper) but the lower bound was non-constant" |

536 | raise ValueError, msg % (conData.name,) |

537 | if not expr._args[2].is_constant(): |

538 | msg = "Constraint '%s' found a double-sided "\ |

539 | "inequality expression (lower <= expression "\ |

540 | "<= upper) but the upper bound was non-constant" |

541 | raise ValueError, msg % (conData.name,) |

542 | conData.lower = expr._args[0] |

543 | conData.body = expr._args[1] |

544 | conData.upper = expr._args[2] |

545 | conData.lower_ineq_strict = expr._strict[0] |

546 | conData.upper_ineq_strict = expr._strict[1] |

547 | else: |

548 | if expr._args[1].is_constant(): |

549 | conData.lower = None |

550 | conData.body = expr._args[0] |

551 | conData.upper = expr._args[1] |

552 | conData.upper_ineq_strict = expr._strict[0] |

553 | elif expr._args[0].is_constant(): |

554 | conData.lower = expr._args[0] |

555 | conData.body = expr._args[1] |

556 | conData.upper = None |

557 | conData.lower_ineq_strict = expr._strict[0] |

558 | else: |

559 | conData.lower = None |

560 | conData.body = generate_expression('_','sub', expr._args[0], expr._args[1]) |

561 | conData.upper = NumericConstant(None,None,0) |

562 | conData.upper_ineq_strict = expr._strict[0] |

563 | |

564 | # |

565 | # Replace numeric bound values with a NumericConstant object, |

566 | # and reset the values to 'None' if they are 'infinite' |

567 | # |

568 | if conData.lower is not None: |

569 | val = conData.lower() |

570 | if not pyutilib.math.is_finite(val): |

571 | if val > 0: |

572 | msg = "Constraint '%s' created with a +Inf lower bound" |

573 | raise ValueError, msg % ( conData.name, ) |

574 | conData.lower = None |

575 | conData.lower_ineq_strict = False |

576 | elif bool(val > 0) == bool(val <= 0): |

577 | msg = "Constraint '%s' created with a non-numeric lower bound" |

578 | raise ValueError, msg % ( conData.name, ) |

579 | if conData.upper is not None: |

580 | val = conData.upper() |

581 | if not pyutilib.math.is_finite(val): |

582 | if val < 0: |

583 | msg = "Constraint '%s' created with a -Inf upper bound" |

584 | raise ValueError, msg % ( conData.name, ) |

585 | conData.upper = None |

586 | conData.upper_ineq_strict = False |

587 | elif bool(val > 0) == bool(val <= 0): |

588 | msg = "Constraint '%s' created with a non-numeric upper bound" |

589 | raise ValueError, msg % ( conData.name, ) |

590 | |

591 | # Error check, to ensure that we don't have a constraint that |

592 | # doesn't depend on any variables / parameters |

593 | # |

594 | # TBD: This is an expensive test (is_constant() is recursive). |

595 | # The pre-coopr-2.5 version of this test was basically never |

596 | # executed (just looked for int, float, etc.) It is not clear |

597 | # that running this debugging test is even worth it [JDS, 28 Feb 2011]. |

598 | # |

599 | #if conData.body.is_constant(): |

600 | # feasible = True |

601 | # value = conData.body() |

602 | # if conData.lower is not None: |

603 | # if conData.lower_ineq_strict: |

604 | # feasible = feasible and (conData.lower() < value) |

605 | # else: |

606 | # feasible = feasible and (conData.lower() <= value) |

607 | # if conData.upper is not None: |

608 | # if conData.upper_ineq_strict: |

609 | # feasible = feasible and (value < conData.upper()) |

610 | # else: |

611 | # feasible = feasible and (value < conData.upper()) |

612 | # if feasible: |

613 | # logger.warn("Constraint '%s' has a constant constant body.") |

614 | # else: |

615 | # logger.error("Constraint '%s' has an constant infeasible " |

616 | # "constant body.") |

617 | |

618 | # Error check, to ensure that we don't have an equality constraint with |

619 | # 'infinite' RHS |

620 | # |

621 | if conData._equality: |

622 | if conData.lower != conData.upper: #pragma:nocover |

623 | msg = "Equality constraint '%s' has non-equal lower and "\ |

624 | "upper bounds (this is indicitive of a SERIOUS "\ |

625 | "internal error in Pyomo)." |

626 | raise RuntimeError, msg % self.name |

627 | if conData.lower is None: |

628 | msg = "Equality constraint '%s' defined with non-finite term" |

629 | raise ValueError, msg % self.name |

630 | |

631 | # |

632 | # hook up the constraint data object to the parent constraint. |

633 | # |

634 | self._data[index] = conData |

635 | |

636 | # |

637 | # update the parent model's (if any) label->constraint map. |

638 | # |

639 | if self.model is not None: |

640 | self.model()._label_constraint_map[conData.label] = conData |

641 | |

642 | |

643 | def pprint(self, ostream=None): |

644 | if ostream is None: |

645 | ostream = sys.stdout |

646 | print >>ostream, " ",self.name,":", |

647 | print >>ostream, "\tSize="+str(len(self._data.keys())), |

648 | if isinstance(self._index,_BaseSet): |

649 | print >>ostream, "\tIndex=",self._index.name |

650 | else: |

651 | print >>ostream,"" |

652 | for val in self._data: |

653 | if not val is None: |

654 | print >>ostream, "\t"+`val` |

655 | if self._data[val].lower is not None: |

656 | print >>ostream, "\t\t", |

657 | if self._data[val].lower.is_expression(): |

658 | self._data[val].lower.pprint(ostream) |

659 | else: |

660 | print >>ostream, str(self._data[val].lower) |

661 | else: |

662 | print >>ostream, "\t\t-Inf" |

663 | if self._data[val].lower_ineq_strict: |

664 | print >>ostream, "\t\t<" |

665 | else: |

666 | print >>ostream, "\t\t<=" |

667 | if self._data[val].body is not None: |

668 | print >>ostream, "\t\t", |

669 | if self._data[val].body.is_expression(): |

670 | self._data[val].body.pprint(ostream) |

671 | else: |

672 | print >>ostream, str(self._data[val].body) |

673 | #else: #pragma:nocover |

674 | #raise ValueError, "Unexpected empty constraint body" |

675 | if self._data[val].upper_ineq_strict: |

676 | print >>ostream, "\t\t<" |

677 | else: |

678 | print >>ostream, "\t\t<=" |

679 | if self._data[val].upper is not None: |

680 | print >>ostream, "\t\t", |

681 | if self._data[val].upper.is_expression(): |

682 | self._data[val].upper.pprint(ostream) |

683 | else: |

684 | print >>ostream, str(self._data[val].upper) |

685 | elif self._data[val]._equality: |

686 | print >>ostream, "\t\t", |

687 | if self._data[val].lower.is_expression(): |

688 | self._data[val].lower.pprint(ostream) |

689 | else: |

690 | print >>ostream, str(self._data[val].lower) |

691 | else: |

692 | print >>ostream, "\t\tInf" |

693 | |

694 | def display(self, prefix="", ostream=None): |

695 | if ostream is None: |

696 | ostream = sys.stdout |

697 | print >>ostream, prefix+"Constraint "+self.name,":", |

698 | print >>ostream, " Size="+str(len(self)) |

699 | if None in self._data: |

700 | if self._data[None].body is None: |

701 | val = 'none' |

702 | else: |

703 | val = pyutilib.misc.format_io(self._data[None].body()) |

704 | print >>ostream, '%s Value=%s' % (prefix, val) |

705 | else: |

706 | flag=True |

707 | for key in self._data: |

708 | if not self._data[key].active: |

709 | continue |

710 | if flag: |

711 | print >>ostream, prefix+" \tLower\tBody\t\tUpper" |

712 | flag=False |

713 | if self._data[key].lower is not None: |

714 | lval = str(self._data[key].lower()) |

715 | else: |

716 | lval = "-Infinity" |

717 | val = str(self._data[key].body()) |

718 | if self._data[key].upper is not None: |

719 | uval = str(self._data[key].upper()) |

720 | else: |

721 | uval = "Infinity" |

722 | print >>ostream, "%s %s :\t%s\t%s\t%s" % ( |

723 | prefix, str(key), lval, val, uval ) |

724 | if flag: |

725 | print >>ostream, prefix+" None active" |

726 | |

727 | |

728 | |

729 | class ConstraintList(Constraint): |

730 | """ |

731 | A constraint component that represents a list of constraints. Constraints can |

732 | be indexed by their index, but when they are added an index value is not specified. |

733 | """ |

734 | |

735 | alias("ConstraintList", "A list of constraints in a model.") |

736 | |

737 | End = (1003,) |

738 | |

739 | def __del__(self): |

740 | if Constraint is not None: |

741 | Constraint.__del__(self) |

742 | |

743 | def __init__(self, *args, **kwargs): |

744 | if len(args) > 0: |

745 | raise ValueError, "Cannot specify indices for a ConstraintList object" |

746 | |

747 | # Construct parents |

748 | kwargs['ctype'] = kwargs.get('ctype', ConstraintList) |

749 | self._hidden_index = Set() |

750 | self._nconstraints = 0 |

751 | targs = [self._hidden_index] |

752 | Constraint.__init__(self, *targs, **kwargs) |

753 | #self._no_rule_init = kwargs.pop('noruleinit', None ) |

754 | #tmprule = kwargs.pop('rule', None ) |

755 | #tmprule = kwargs.pop('expr', tmprule ) |

756 | |

757 | def construct(self, *args, **kwds): |

758 | self._hidden_index.construct() |

759 | Constraint.construct(self, *args, **kwds) |

760 | |

761 | def construct(self, data=None): |

762 | # |

763 | if __debug__: |

764 | logger.debug("Constructing constraint %s",self.name) |

765 | if (self._no_rule_init is not None) and (self.rule is not None): |

766 | msg = 'WARNING: noruleinit keyword is being used in conjunction ' \ |

767 | "with rule keyword for constraint '%s'; defaulting to " \ |

768 | 'rule-based construction.' |

769 | print msg % self.name |

770 | if self.rule is None: |

771 | return |

772 | if self._constructed: |

773 | return |

774 | self._constructed=True |

775 | # |

776 | # Local variables for code optimization |

777 | # |

778 | _self_rule = self.rule |

779 | _self_model = self.model() |

780 | # |

781 | while True: |

782 | val = self._nconstraints + 1 |

783 | if __debug__: |

784 | if logger.isEnabledFor(logging.DEBUG): |

785 | logger.debug(" Constructing constraint index "+str(val)) |

786 | expr = apply_indexed_rule( self, _self_rule, _self_model, val ) |

787 | if expr is None: |

788 | logger.warning("DEPRECATION WARNING: Constraint rule returned None instead of ConstraintList.End") |

789 | break |

790 | if (expr.__class__ is tuple and expr == ConstraintList.End) or \ |

791 | (type(expr) in (int, long, float) and expr == 0): |

792 | break |

793 | self.add(expr) |

794 | |

795 | def add(self, *args): |

796 | self._nconstraints += 1 |

797 | targs = [self._nconstraints] + list(args) |

798 | Constraint.add(self, *targs) |

799 | |

800 | |

801 | |

802 | class SOSConstraint(ConstraintBase): |

803 | """ |

804 | Represents an SOS-n constraint. |

805 | |

806 | Usage: |

807 | model.C1 = SOSConstraint( |

808 | [...], |

809 | var=VAR, |

810 | [set=SET OR index=SET], |

811 | [sos=N OR level=N] |

812 | ) |

813 | [...] Any number of sets used to index SET |

814 | VAR The set of variables making up the SOS. Indexed by SET. |

815 | SET The set used to index VAR. SET is optionally indexed by |

816 | the [...] sets. If SET is not specified, VAR is indexed |

817 | over the set(s) it was defined with. |

818 | N This constraint is an SOS-N constraint. Defaults to 1. |

819 | |

820 | Example: |

821 | |

822 | model = AbstractModel() |

823 | model.A = Set() |

824 | model.B = Set(A) |

825 | model.X = Set(B) |

826 | |

827 | model.C1 = SOSConstraint(model.A, var=model.X, set=model.B, sos=1) |

828 | |

829 | This constraint actually creates one SOS-1 constraint for each |

830 | element of model.A (e.g., if |A| == N, there are N constraints). |

831 | In each constraint, model.X is indexed by the elements of |

832 | model.D[a], where 'a' is the current index of model.A. |

833 | |

834 | model = AbstractModel() |

835 | model.A = Set() |

836 | model.X = Var(model.A) |

837 | |

838 | model.C2 = SOSConstraint(var=model.X, sos=2) |

839 | |

840 | This produces exactly one SOS-2 constraint using all the variables |

841 | in model.X. |

842 | """ |

843 | |

844 | |

845 | alias("SOSConstraint", "SOS constraint expressions in a model.") |

846 | |

847 | def __init__(self, *args, **kwargs): |

848 | name = kwargs.get('name', 'unknown') |

849 | |

850 | # Get the 'var' parameter |

851 | sosVars = kwargs.pop('var', None) |

852 | |

853 | # Get the 'set' or 'index' parameters |

854 | if 'set' in kwargs and 'index' in kwargs: |

855 | raise TypeError, "Specify only one of 'set' and 'index' -- " \ |

856 | "they are equivalent parameters" |

857 | sosSet = kwargs.pop('set', None) |

858 | sosSet = kwargs.pop('index', sosSet) |

859 | |

860 | # Get the 'sos' or 'level' parameters |

861 | if 'sos' in kwargs and 'index' in kwargs: |

862 | raise TypeError, "Specify only one of 'sos' and 'level' -- " \ |

863 | "they are equivalent parameters" |

864 | sosLevel = kwargs.pop('sos', None) |

865 | sosLevel = kwargs.pop('level', sosLevel) |

866 | |

867 | # Make sure sosLevel has been set |

868 | if sosLevel is None: |

869 | raise TypeError, "SOSConstraint() requires that either the " \ |

870 | "'sos' or 'level' keyword arguments be set to indicate " \ |

871 | "the type of SOS." |

872 | |

873 | # Make sure we have a variable |

874 | if sosVars is None: |

875 | raise TypeError, "SOSConstraint() requires the 'var' keyword " \ |

876 | "be specified" |

877 | |

878 | # Find the default sets for sosVars if sosSets is None |

879 | if sosSet is None: |

880 | sosSet = sosVars.index() |

881 | |

882 | # Construct parents |

883 | kwargs['ctype'] = kwargs.get('ctype', SOSConstraint) |

884 | ConstraintBase.__init__(self, *args, **kwargs) |

885 | |

886 | # Set member attributes |

887 | self._sosVars = sosVars |

888 | self._sosSet = sosSet |

889 | self._sosLevel = sosLevel |

890 | |

891 | # TODO figure out why exactly the code expects this to be defined |

892 | # likely due to Numericvalue |

893 | self.domain = None |

894 | |

895 | # TODO should variables be ordered? |

896 | |

897 | def construct(self, *args, **kwds): |

898 | """ |

899 | A quick hack to call add after data has been loaded. construct |

900 | doesn't actually need to do anything. |

901 | """ |

902 | self.add() |

903 | |

904 | def add(self, *args): |

905 | """ |

906 | Mimics Constraint.add, but is only used to alert preprocessors |

907 | to the existence of the SOS variables. |

908 | |

909 | Ignores all arguments passed to it. |

910 | """ |

911 | |

912 | # self._data is a ConstraintData object, which has a member |

913 | # .body. .body needs to have a 3-tuple of expressions |

914 | # containing the variables that are referenced by this |

915 | # constraint. We only use one index, None. I have no |

916 | # particular reason to use None, it just seems consistent with |

917 | # what Constraint objects do. |

918 | |

919 | # For simplicity, we sum over the variables. |

920 | |

921 | vars = self.sos_vars() |

922 | |

923 | expr = sum(vars[i] for i in vars._index) |

924 | |

925 | self._data[None] = ConstraintData(create_name(self.name, None), self) |

926 | self._data[None].body = expr |

927 | |

928 | def sos_level(self): |

929 | """ Return n, where this class is an SOS-n constraint """ |

930 | return self._sosLevel |

931 | |

932 | def sos_vars(self): |

933 | """ Return the variables in the SOS """ |

934 | return self._sosVars |

935 | |

936 | def sos_set(self): |

937 | """ Return the set used to index the variables """ |

938 | return self._sosSet |

939 | |

940 | def sos_set_set(self): |

941 | """ Return the sets used to index the sets indexing the variables """ |

942 | return self._index |

943 | |

944 | def pprint(self, ostream=None): |

945 | if ostream is None: |

946 | ostream = sys.stdout |

947 | print >>ostream, " ",self.name,":", |

948 | print >>ostream, "Type="+str(self._sosLevel) |

949 | print >>ostream, "\tVariable: "+self._sosVars.name |

950 | print >>ostream, "\tIndices: ", |

951 | for i in self._sosSet.value: |

952 | print >>ostream, str(i)+" ", |

953 | print >>ostream, "" |

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