-
Notifications
You must be signed in to change notification settings - Fork 518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Constraint: only store the original expression (not lower/body/upper) #3293
Conversation
(something caused gdpopt to set variables to int instead of floats)
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3293 +/- ##
==========================================
- Coverage 88.49% 88.32% -0.18%
==========================================
Files 868 868
Lines 98436 98419 -17
==========================================
- Hits 87115 86928 -187
- Misses 11321 11491 +170
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mostly looks good (nice sneaky FBBT additions!), but I have quibbles with the naming of normalize_constraint
.
pyomo/core/base/constraint.py
Outdated
body = value(self.body, exception=exception) | ||
return body | ||
|
||
def normalize_constraint(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I like the name of this method... 'Normalize' makes me think of means of 1 and stuff... Could it be tupelize_constraint
or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not too keen on tuplize_constraint
(it doesn't feel very descriptive); but I agree that normalize_constraint
is probably worse. What about standardize_constraint
or to_range_constraint
(the latter is a bit misleading as it does not return a RangedExpression
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm. I actually kind of like to_range_constraint
... Maybe to_tuple
(also not super descriptive, but since we accept Constraint expressions in that form, it kinda makes sense)? I don't love standardize_constraint
because of potential standard form connotations, but I do like it a lot better than normalize_constraint
!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to_bounded_expression()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oooo, I think I like that one!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented!
# | ||
# Normalize the incoming expressions, if we can | ||
# |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very beautiful block of deleted code! :D
self.assertEqual(model.c.lower, None) | ||
self.assertEqual(model.c.lower, float('-inf')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change backwards compatible? Or were we testing for the existence of a bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were testing for the existence of a bug: By (current) convention, lower
returns the expression for the lower bound and lb
returns the value (or None
if not specified or -inf
). The problem was that in the old world order, some bounds processing was done "early" in set_value
-- including some of the conversion from +/-inf to None
. We now defer that processing until the user asks for the value. So, in this test, since the user provided the "expression" float('-inf')
for the lower bound, the "correct" thing to do is return that expression from lower
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had one minor nitpick about a missed close-parenthesis but otherwise, I'm happy with it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect that this section in pyomo.contrib.sensitivity_toolbox
can also be simplified with this change:
# Constraint must be a ranged inequality, break into |
|
||
def rule(model): | ||
return (float('inf'), model.x) | ||
model = self.create_model(abstract=True).create_instance() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not change this to just create a ConcreteModel
? Are there behaviors we expect with AbstractModels
that are no longer being tested because of this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could just switch this over to a ConcreteModel. I left it as it was mostly to minimize changes in old code / tests. The biggest reason for the change here was to remove the test for an exception on model creation, as this PR defers the bounds sanity checking until later (model compilation step and not model construction step).
|
||
model.c = Constraint(rule=rule) | ||
self.assertRaises(ValueError, model.create_instance) | ||
model = self.create_model(abstract=True).create_instance() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question about just using ConcreteModel
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above: we certainly could change these tests, but I didn't to minimize (unnecessary) changes to old code.
Co-authored-by: Bethany Nicholson <blnicho@users.noreply.github.com>
Co-authored-by: Bethany Nicholson <blnicho@users.noreply.github.com>
@blnicho: I went ahead and updated the logic in |
@jsiirola I don't see the changes to |
…nly' into constraint-store-expr-only
Yup. Well - no. I ran |
Fixes # .
Summary/Motivation:
This PR changes the internal data store for Constraint to only store the original (relational) expression and not break it apart into (
lower
,body
, andupper
). Those attributes are preserved as properties (and the expression rearrangement happens dynamically when they are called). Most clients who need the expression "standardized" should use the newnormalize_constraint()
method (which is more efficient, as it avoids duplicate work and unnecessary calls toas_numeric()
).While this would appear to be strictly cosmetic, this change is the first part of the templatized LP writer.
Changes proposed in this PR:
_lower
,_body
, and_upper
ConstraintData attributes.args
as the only public API for getting at an expression's argumentsexpr
(and notbody
) to detect changed expressionscompute_bounds_on_expression
Legal Acknowledgement
By contributing to this software project, I have read the contribution guide and agree to the following terms and conditions for my contribution: