Skip to content
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

contrib.fbbt, pyomo.gdp: Adding new walker for compute_bounds_on_expr #3027

Merged
merged 41 commits into from
Nov 8, 2023

Conversation

emma58
Copy link
Contributor

@emma58 emma58 commented Oct 31, 2023

Fixes # .

Summary/Motivation:

The majority of the time in the gdp.bigm transformation is spent calculating M values, mostly in the function (from fbbt) compute_bounds_on_expr. This PR adds a new walker to contrib.fbbt specifically for computing bounds on expressions based on Var bounds. It is very similar to the leaf-to-root walker, but not identical as it is designed for only a single pass (unlike in actual fbbt), and it caches only the leaf node bounds. Moving the bigm transformations onto it helps performance in calculating M values significantly.

Changes proposed in this PR:

  • Adds ExpressionBoundsVisitor to contrib.fbbt
  • Has compute_bounds_on_expr wrap ExpressionBoundsVisitor instead of _FBBTVisitorLeafToRoot
  • (unrelatedly) rewrites _FBBTVisitorLeafToRoot, moving it onto the StreamBasedExpressionVisitor base class
  • Rewrites the implementation of mul in interval.py: This is actually quite significant performance-wise.
  • Rewrites bigm calculations in gdp.bigm and gdp.mbigm to store an instance of ExpressionBoundsVisitor for all of apply_to to take advantage of caching, rather than relying on compute_bounds_on_expr.

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:

  1. I agree my contributions are submitted under the BSD license.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

…r, which may or may not matter for this exercise...
…igm, which basically means caching Var bounds info for the whole transformation
…e bigm transformations only building on visitor, and caching the leaf bounds
Copy link

codecov bot commented Oct 31, 2023

Codecov Report

Attention: 5 lines in your changes are missing coverage. Please review.

Comparison is base (70ef6e4) 87.88% compared to head (f9edf74) 87.89%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3027      +/-   ##
==========================================
+ Coverage   87.88%   87.89%   +0.01%     
==========================================
  Files         769      770       +1     
  Lines       89555    89662     +107     
==========================================
+ Hits        78705    78812     +107     
  Misses      10850    10850              
Flag Coverage Δ
linux 85.22% <98.21%> (+0.01%) ⬆️
osx 75.00% <98.21%> (+0.02%) ⬆️
other 85.40% <98.21%> (+0.01%) ⬆️
win 82.45% <98.21%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
pyomo/contrib/fbbt/interval.py 96.38% <100.00%> (+0.01%) ⬆️
pyomo/gdp/plugins/bigm.py 98.13% <100.00%> (+0.08%) ⬆️
pyomo/gdp/plugins/bigm_mixin.py 94.16% <100.00%> (+0.14%) ⬆️
pyomo/contrib/fbbt/expression_bounds_walker.py 99.15% <99.15%> (ø)
pyomo/gdp/plugins/multiple_bigm.py 96.94% <91.66%> (-0.31%) ⬇️
pyomo/contrib/fbbt/fbbt.py 96.49% <97.52%> (-0.47%) ⬇️

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

pyomo/contrib/fbbt/expression_bounds_walker.py Outdated Show resolved Hide resolved
pyomo/contrib/fbbt/expression_bounds_walker.py Outdated Show resolved Hide resolved
self,
leaf_bounds=None,
feasibility_tol=1e-8,
use_fixed_var_values_as_bounds=False,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expected this default to be True?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did False because it seems safer to me... Because then the bounds are right regardless of if variables get unfixed later. Basically this way we get positive confirmation from a user that they know things are potentially invalid if they unfix Vars.

return False, None


def _before_other(visitor, child):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this handler for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous walker basically had a default that if it didn't understand what was going on, it still returned bounds of (-inf, inf). So this maintains that behavior. I'm not sure that it's a good idea long-term though?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should toss a warning that your expression had things we weren't expecting?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should, but that's actually going to require a more significant rewrite because right now this is catching both things we don't understand and things that we do understand, just don't need a beforeChild handler for (like SumExpression). So I think it would make the most sense to move this onto the new BeforeChildDispatcher base class and then finally get pedantic about when we're confused and when we just don't care. But I'd rather that be a separate PR.

pyomo/contrib/fbbt/fbbt.py Outdated Show resolved Hide resolved
Copy link
Member

@jsiirola jsiirola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this, but I do wonder that if we are going to start passing the visitor as an argument to all of the handlers, then why are they not just methods on the visitor class? That would look more "normal".

return False, None


def _before_other(visitor, child):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should toss a warning that your expression had things we weren't expecting?

elif not child.is_potentially_variable():
handlers[child_type] = _before_NPV
else:
handlers[child_type] = _before_other
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like if we hit this, then we should warn about an unknown / unhandled expression type?

@emma58 emma58 merged commit 555c10c into Pyomo:main Nov 8, 2023
27 checks passed
@emma58 emma58 deleted the bigm-calc-performance branch November 8, 2023 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants