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

pyomo.gdp: Adding gdp.bound_pretransformation #2824

Merged
merged 36 commits into from
May 19, 2023

Conversation

emma58
Copy link
Contributor

@emma58 emma58 commented May 5, 2023

Fixes # .

Summary/Motivation:

Finally, it's here...! This PR adds a "pre-transformation" to pyomo.gdp that finds univariate constraints and transforms them according the the special transformation in the Balas 1988 paper. For example, given a disjunction that looks like this:

$$\begin{bmatrix} Y_1 \\\ -10 \leq x \leq 11 \\\ \begin{bmatrix} W_1 \\\ x <= 3 \end{bmatrix} \lor \begin{bmatrix} W_2 \\\ x >= -17 \end{bmatrix} \end{bmatrix} \lor \begin{bmatrix} Y_2 \\\ x = 0 \end{bmatrix},$$

we create the transformed constraints,

$$-10w_1 -17w_2 \leq x \leq 3w_1 + 11w_2$$

(where $w_1$ and $w_2$ are the binary variables corresponding to Booleans $W_1$ and $W_2$).

This transformation will transform every univariate disjunctive Constraint for which is has enough information (defaulting to the tighter of variable bounds and bounds given in global constraints if the information is not on the disjunctive part of the model itself). All multivariate constraints and any univariate constraints on unbounded variables without corresponding constraints in other Disjuncts will remain active. In addition, this transformation does not go all the way to a MIP. It is intended to be followed by one of the GDP-to-MIP transformations to transform the logical parts of the model and any Constraints this transformation could not transform.

In terms of implementation, the basic idea is that, for each Disjunction, it does a first pass down the GDP hierarchy, keeping track of the tightest bound it finds there for any variables appearing in univariate Constraints. Then, for each of those variables, it attempts to construct two constraints: one for lower bounds and one for upper bounds, by finding the tightest bound at each of the leaf nodes in the hierarchy. It may have to traverse back upwards to find bounds in the ancestors of the leaf, but we cache them when we do this. If ever it cannot find a bound, it bails on the entire constraint. It accepts targets, and if the targets are nested in each other, it will transform from the highest target in the GDP tree (so the nested ones are essentially ignored).

Changes proposed in this PR:

  • Adds gdp.bound_pretransformation transformation.
  • Adds some functionality to the GDPTree class to support traversing through the Disjuncts in a GDP forest, iterating through the leaves, etc.

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.

emma58 added 29 commits April 6, 2023 11:17
…apping from the original model to the transformed one
…some bugs with the deferred deactivation of constraints
…ore than one variable bounded in the disjunction
…that is, we transform the upper or lower part, but not both
…ing to the tighter of them and the variable bounds when there's nothing better in the GDP hierarchy, adding tests
@codecov
Copy link

codecov bot commented May 5, 2023

Codecov Report

Patch coverage: 97.05% and project coverage change: +0.02 🎉

Comparison is base (2264877) 87.16% compared to head (9c7001d) 87.19%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2824      +/-   ##
==========================================
+ Coverage   87.16%   87.19%   +0.02%     
==========================================
  Files         764      765       +1     
  Lines       88279    88471     +192     
==========================================
+ Hits        76949    77138     +189     
- Misses      11330    11333       +3     
Flag Coverage Δ
linux 84.25% <97.05%> (+0.02%) ⬆️
osx 73.90% <97.05%> (+0.04%) ⬆️
other 84.42% <97.05%> (+0.02%) ⬆️
win 81.79% <97.05%> (+0.02%) ⬆️

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

Impacted Files Coverage Δ
pyomo/gdp/util.py 89.96% <85.71%> (-0.98%) ⬇️
pyomo/gdp/plugins/bound_pretransformation.py 98.30% <98.30%> (ø)
pyomo/gdp/disjunct.py 83.97% <100.00%> (+0.03%) ⬆️
pyomo/gdp/plugins/__init__.py 100.00% <100.00%> (ø)
pyomo/gdp/plugins/multiple_bigm.py 97.22% <100.00%> (ø)

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@emma58 emma58 requested a review from jsiirola May 12, 2023 15:14
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.

One thing that I think needs to be changed (using include_fixed=False); I think everything else is optional.

pyomo/gdp/plugins/bound_pretransformation.py Outdated Show resolved Hide resolved
# on v and wait for later
if not is_root:
v_bounds['to_deactivate'].add(constraint)
elif len(list(identify_variables(constraint.body))) == 1:
Copy link
Member

Choose a reason for hiding this comment

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

We might want to consider another way to do this: as written, this will always walk the entire expression tree, even if it knows that the constraint can be skipped after the second variable. Maybe something like:

var_gen = identify_variables(constraint.body, include_fixed=False)
try:
    next(var_gen)
except StopIteration:
    continue
try:
    next(var_gen)
    continue
except StopIteration:
    pass
repn = generate_standard_repn(constraint.body)
# ...

Also note that it is important to include include_fixed=False, too...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, this makes sense. Is it bad style to just do everything in the except block of the second try-except? (I'm doing that for now.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, good catch on the fixed Vars! I added a test.

pyomo/gdp/plugins/bound_pretransformation.py Outdated Show resolved Hide resolved
@jsiirola jsiirola merged commit 47deef1 into Pyomo:main May 19, 2023
@emma58 emma58 deleted the gdp-bound-pretransformation branch February 15, 2024 17:17
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.

2 participants