Skip to content

Commit 43a6446

Browse files
authored
Merge pull request #3893 from jsiirola/constraint-equality
Improved `ConstraintData.equality`, relational expression generation
2 parents e55e4a2 + 8df5c4f commit 43a6446

11 files changed

Lines changed: 3788 additions & 1523 deletions

File tree

examples/pyomobook/blocks-ch/blocks_gen.txt

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
2 Var Declarations
2323
Power : Size=5, Index=TIME
2424
Key : Lower : Value : Upper : Fixed : Stale : Domain
25-
0 : 0 : 120.0 : 500.0 : False : False : Reals
26-
1 : 0 : 145.0 : 500.0 : False : False : Reals
27-
2 : 0 : 119.0 : 500.0 : False : False : Reals
28-
3 : 0 : 42.0 : 500.0 : False : False : Reals
29-
4 : 0 : 190.0 : 500.0 : False : False : Reals
25+
0 : 0 : 120.0 : 500 : False : False : Reals
26+
1 : 0 : 145.0 : 500 : False : False : Reals
27+
2 : 0 : 119.0 : 500 : False : False : Reals
28+
3 : 0 : 42.0 : 500 : False : False : Reals
29+
4 : 0 : 190.0 : 500 : False : False : Reals
3030
UnitOn : Size=5, Index=TIME
3131
Key : Lower : Value : Upper : Fixed : Stale : Domain
3232
0 : 0 : None : 1 : False : True : Binary
@@ -46,11 +46,11 @@
4646

4747
1 Constraint Declarations
4848
limit_ramp : Size=4, Index=TIME, Active=True
49-
Key : Lower : Body : Upper : Active
50-
1 : -50.0 : Generator[G_EAST].Power[1] - Generator[G_EAST].Power[0] : Generator[G_EAST].RampLimit : True
51-
2 : -50.0 : Generator[G_EAST].Power[2] - Generator[G_EAST].Power[1] : Generator[G_EAST].RampLimit : True
52-
3 : -50.0 : Generator[G_EAST].Power[3] - Generator[G_EAST].Power[2] : Generator[G_EAST].RampLimit : True
53-
4 : -50.0 : Generator[G_EAST].Power[4] - Generator[G_EAST].Power[3] : Generator[G_EAST].RampLimit : True
49+
Key : Lower : Body : Upper : Active
50+
1 : -50.0 : Generator[G_EAST].Power[1] - Generator[G_EAST].Power[0] : 50.0 : True
51+
2 : -50.0 : Generator[G_EAST].Power[2] - Generator[G_EAST].Power[1] : 50.0 : True
52+
3 : -50.0 : Generator[G_EAST].Power[3] - Generator[G_EAST].Power[2] : 50.0 : True
53+
4 : -50.0 : Generator[G_EAST].Power[4] - Generator[G_EAST].Power[3] : 50.0 : True
5454

5555
7 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef Cost
5656
Generator[G_MAIN] : Active=True
@@ -67,11 +67,11 @@
6767
2 Var Declarations
6868
Power : Size=5, Index=TIME
6969
Key : Lower : Value : Upper : Fixed : Stale : Domain
70-
0 : 0 : 120.0 : 500.0 : False : False : Reals
71-
1 : 0 : 145.0 : 500.0 : False : False : Reals
72-
2 : 0 : 119.0 : 500.0 : False : False : Reals
73-
3 : 0 : 42.0 : 500.0 : False : False : Reals
74-
4 : 0 : 190.0 : 500.0 : False : False : Reals
70+
0 : 0 : 120.0 : 500 : False : False : Reals
71+
1 : 0 : 145.0 : 500 : False : False : Reals
72+
2 : 0 : 119.0 : 500 : False : False : Reals
73+
3 : 0 : 42.0 : 500 : False : False : Reals
74+
4 : 0 : 190.0 : 500 : False : False : Reals
7575
UnitOn : Size=5, Index=TIME
7676
Key : Lower : Value : Upper : Fixed : Stale : Domain
7777
0 : 0 : None : 1 : False : True : Binary
@@ -91,11 +91,11 @@
9191

9292
1 Constraint Declarations
9393
limit_ramp : Size=4, Index=TIME, Active=True
94-
Key : Lower : Body : Upper : Active
95-
1 : -50.0 : Generator[G_MAIN].Power[1] - Generator[G_MAIN].Power[0] : Generator[G_MAIN].RampLimit : True
96-
2 : -50.0 : Generator[G_MAIN].Power[2] - Generator[G_MAIN].Power[1] : Generator[G_MAIN].RampLimit : True
97-
3 : -50.0 : Generator[G_MAIN].Power[3] - Generator[G_MAIN].Power[2] : Generator[G_MAIN].RampLimit : True
98-
4 : -50.0 : Generator[G_MAIN].Power[4] - Generator[G_MAIN].Power[3] : Generator[G_MAIN].RampLimit : True
94+
Key : Lower : Body : Upper : Active
95+
1 : -50.0 : Generator[G_MAIN].Power[1] - Generator[G_MAIN].Power[0] : 50.0 : True
96+
2 : -50.0 : Generator[G_MAIN].Power[2] - Generator[G_MAIN].Power[1] : 50.0 : True
97+
3 : -50.0 : Generator[G_MAIN].Power[3] - Generator[G_MAIN].Power[2] : 50.0 : True
98+
4 : -50.0 : Generator[G_MAIN].Power[4] - Generator[G_MAIN].Power[3] : 50.0 : True
9999

100100
7 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef Cost
101101

@@ -124,11 +124,11 @@
124124
2 Var Declarations
125125
Power : Size=5, Index=TIME
126126
Key : Lower : Value : Upper : Fixed : Stale : Domain
127-
0 : 0 : 120.0 : 500.0 : False : False : Reals
128-
1 : 0 : 145.0 : 500.0 : False : False : Reals
129-
2 : 0 : 119.0 : 500.0 : False : False : Reals
130-
3 : 0 : 42.0 : 500.0 : False : False : Reals
131-
4 : 0 : 190.0 : 500.0 : False : False : Reals
127+
0 : 0 : 120.0 : 500 : False : False : Reals
128+
1 : 0 : 145.0 : 500 : False : False : Reals
129+
2 : 0 : 119.0 : 500 : False : False : Reals
130+
3 : 0 : 42.0 : 500 : False : False : Reals
131+
4 : 0 : 190.0 : 500 : False : False : Reals
132132
UnitOn : Size=5, Index=TIME
133133
Key : Lower : Value : Upper : Fixed : Stale : Domain
134134
0 : 0 : None : 1 : False : True : Binary
@@ -148,11 +148,11 @@
148148

149149
1 Constraint Declarations
150150
limit_ramp : Size=4, Index=TIME, Active=True
151-
Key : Lower : Body : Upper : Active
152-
1 : -50.0 : Generator[G_EAST].Power[1] - Generator[G_EAST].Power[0] : Generator[G_EAST].RampLimit : True
153-
2 : -50.0 : Generator[G_EAST].Power[2] - Generator[G_EAST].Power[1] : Generator[G_EAST].RampLimit : True
154-
3 : -50.0 : Generator[G_EAST].Power[3] - Generator[G_EAST].Power[2] : Generator[G_EAST].RampLimit : True
155-
4 : -50.0 : Generator[G_EAST].Power[4] - Generator[G_EAST].Power[3] : Generator[G_EAST].RampLimit : True
151+
Key : Lower : Body : Upper : Active
152+
1 : -50.0 : Generator[G_EAST].Power[1] - Generator[G_EAST].Power[0] : 50.0 : True
153+
2 : -50.0 : Generator[G_EAST].Power[2] - Generator[G_EAST].Power[1] : 50.0 : True
154+
3 : -50.0 : Generator[G_EAST].Power[3] - Generator[G_EAST].Power[2] : 50.0 : True
155+
4 : -50.0 : Generator[G_EAST].Power[4] - Generator[G_EAST].Power[3] : 50.0 : True
156156

157157
7 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef Cost
158158
Generator[G_MAIN] : Active=True
@@ -169,11 +169,11 @@
169169
2 Var Declarations
170170
Power : Size=5, Index=TIME
171171
Key : Lower : Value : Upper : Fixed : Stale : Domain
172-
0 : 0 : 120.0 : 500.0 : False : False : Reals
173-
1 : 0 : 145.0 : 500.0 : False : False : Reals
174-
2 : 0 : 119.0 : 500.0 : False : False : Reals
175-
3 : 0 : 42.0 : 500.0 : False : False : Reals
176-
4 : 0 : 190.0 : 500.0 : False : False : Reals
172+
0 : 0 : 120.0 : 500 : False : False : Reals
173+
1 : 0 : 145.0 : 500 : False : False : Reals
174+
2 : 0 : 119.0 : 500 : False : False : Reals
175+
3 : 0 : 42.0 : 500 : False : False : Reals
176+
4 : 0 : 190.0 : 500 : False : False : Reals
177177
UnitOn : Size=5, Index=TIME
178178
Key : Lower : Value : Upper : Fixed : Stale : Domain
179179
0 : 0 : None : 1 : False : True : Binary
@@ -193,11 +193,11 @@
193193

194194
1 Constraint Declarations
195195
limit_ramp : Size=4, Index=TIME, Active=True
196-
Key : Lower : Body : Upper : Active
197-
1 : -50.0 : Generator[G_MAIN].Power[1] - Generator[G_MAIN].Power[0] : Generator[G_MAIN].RampLimit : True
198-
2 : -50.0 : Generator[G_MAIN].Power[2] - Generator[G_MAIN].Power[1] : Generator[G_MAIN].RampLimit : True
199-
3 : -50.0 : Generator[G_MAIN].Power[3] - Generator[G_MAIN].Power[2] : Generator[G_MAIN].RampLimit : True
200-
4 : -50.0 : Generator[G_MAIN].Power[4] - Generator[G_MAIN].Power[3] : Generator[G_MAIN].RampLimit : True
196+
Key : Lower : Body : Upper : Active
197+
1 : -50.0 : Generator[G_MAIN].Power[1] - Generator[G_MAIN].Power[0] : 50.0 : True
198+
2 : -50.0 : Generator[G_MAIN].Power[2] - Generator[G_MAIN].Power[1] : 50.0 : True
199+
3 : -50.0 : Generator[G_MAIN].Power[3] - Generator[G_MAIN].Power[2] : 50.0 : True
200+
4 : -50.0 : Generator[G_MAIN].Power[4] - Generator[G_MAIN].Power[3] : 50.0 : True
201201

202202
7 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef Cost
203203

pyomo/core/base/constraint.py

Lines changed: 35 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@
3737
RangedExpression,
3838
)
3939
from pyomo.core.expr.expr_common import _type_check_exception_arg
40-
from pyomo.core.expr.relational_expr import TrivialRelationalExpression
40+
from pyomo.core.expr.relational_expr import (
41+
TrivialRelationalExpression,
42+
tuple_to_relational_expr,
43+
)
4144
from pyomo.core.expr.template_expr import templatize_constraint
4245
from pyomo.core.base.component import ActiveComponentData, ModelComponentFactory
4346
from pyomo.core.base.global_set import UnindexedComponent_index
@@ -355,10 +358,24 @@ def equality(self):
355358
if expr.__class__ is EqualityExpression:
356359
return True
357360
elif expr.__class__ is RangedExpression:
358-
# TODO: this is a very restrictive form of structural equality.
359361
lb = expr.arg(0)
360-
if lb is not None and lb is expr.arg(2):
361-
return True
362+
if lb is not None:
363+
# Note that checking native_types is sufficient:
364+
# constant expressions should have already been
365+
# simplified by the expression system. If the user
366+
# explicitly created relational expressions with
367+
# constant arguments, then we assume they knew what they
368+
# were doing.
369+
if lb.__class__ in native_types:
370+
ub = expr.arg(2)
371+
if ub.__class__ in native_types:
372+
return lb == ub
373+
else:
374+
# TBD: this is a very restrictive form of structural
375+
# equality. In the future it might be "nice" to
376+
# look for mathematical equivalence - but that is
377+
# expensive and likely not worth the effort.
378+
return lb is expr.arg(2)
362379
return False
363380

364381
@property
@@ -401,61 +418,15 @@ def set_value(self, expr):
401418
"using '<=', '>=', or '=='." % (self.name,)
402419
)
403420
self._expr = expr
404-
return
405421

406422
elif expr.__class__ is tuple: # or expr_type is list:
407-
for arg in expr:
408-
if (
409-
arg is None
410-
or arg.__class__ in native_numeric_types
411-
or isinstance(arg, NumericValue)
412-
):
413-
continue
414-
raise ValueError(
415-
"Constraint '%s' does not have a proper value. "
416-
"Constraint expressions expressed as tuples must "
417-
"contain native numeric types or Pyomo NumericValue "
418-
"objects. Tuple %s contained invalid type, %s"
419-
% (self.name, expr, type(arg).__name__)
420-
)
421-
if len(expr) == 2:
422-
#
423-
# Form equality expression
424-
#
425-
if expr[0] is None or expr[1] is None:
426-
raise ValueError(
427-
"Constraint '%s' does not have a proper value. "
428-
"Equality Constraints expressed as 2-tuples "
429-
"cannot contain None [received %s]" % (self.name, expr)
430-
)
431-
self._expr = EqualityExpression(expr)
432-
return
433-
elif len(expr) == 3:
434-
#
435-
# Form (ranged) inequality expression
436-
#
437-
if expr[0] is None:
438-
self._expr = InequalityExpression(expr[1:], False)
439-
elif expr[2] is None:
440-
self._expr = InequalityExpression(expr[:2], False)
441-
else:
442-
self._expr = RangedExpression(expr, False)
443-
return
444-
else:
445-
raise ValueError(
446-
"Constraint '%s' does not have a proper value. "
447-
"Found a tuple of length %d. Expecting a tuple of "
448-
"length 2 or 3:\n"
449-
" Equality: (left, right)\n"
450-
" Inequality: (lower, expression, upper)"
451-
% (self.name, len(expr))
452-
)
453-
#
454-
# Ignore an 'empty' constraint
455-
#
456-
if expr is Constraint.Skip:
423+
self.set_value(tuple_to_relational_expr(expr))
424+
425+
elif expr is Constraint.Skip:
426+
#
427+
# Ignore (and remove) an 'empty' constraint
428+
#
457429
del self.parent_component()[self.index()]
458-
return
459430

460431
elif expr is None:
461432
raise ValueError(_rule_returned_none_error % (self.name,))
@@ -478,14 +449,14 @@ def set_value(self, expr):
478449
except AttributeError:
479450
pass
480451

481-
raise ValueError(
482-
"Constraint '%s' does not have a proper "
483-
"value. Found %s '%s'\nExpecting a tuple or "
484-
"relational expression. Examples:"
485-
"\n sum(model.costs) == model.income"
486-
"\n (0, model.price[item], 50)"
487-
% (self.name, type(expr).__name__, str(expr))
488-
)
452+
raise ValueError(
453+
"Constraint '%s' does not have a proper "
454+
"value. Found %s '%s'\nExpecting a tuple or "
455+
"relational expression. Examples:"
456+
"\n sum(model.costs) == model.income"
457+
"\n (0, model.price[item], 50)"
458+
% (self.name, type(expr).__name__, str(expr))
459+
)
489460

490461
def lslack(self):
491462
"""

0 commit comments

Comments
 (0)