Skip to content

Adding (reversible) gdp.transform_current_disjunctive_logic transformation#2809

Merged
emma58 merged 25 commits intoPyomo:mainfrom
emma58:reversible-fix-disjunctive-logic
Aug 23, 2023
Merged

Adding (reversible) gdp.transform_current_disjunctive_logic transformation#2809
emma58 merged 25 commits intoPyomo:mainfrom
emma58:reversible-fix-disjunctive-logic

Conversation

@emma58
Copy link
Copy Markdown
Contributor

@emma58 emma58 commented Apr 17, 2023

Related to #277

Summary/Motivation:

The current gdp.fix_disjuncts transformation guarantees that it transforms a GDP all the way to a MI(N)LP. Because this means it needs to call another transformation to transform any LogicalConstraints, this makes it difficult to add a "reverse" transformation to undo the reclassification of the Disjuncts (which is a useful thing for initializing nonlinear GDP models for solving with GDPopt, etc.) Instead of trying to deprecate the going-to-MINLP guarantee, this PR adds a new transformation, gdp.transform_current_disjunctive_state that doesn't even promise to transform all the GDP components: It just goes through all of the Disjunctions, and if any have enough indicator_vars fixed to determine which Disjunct(s) will be selected, it reclassifies the Disjuncts and activates/deactivates accordingly. It currently does nothing with LogicalConstraints, though in the future it would be useful if it took them into account in the logic to decide what Disjuncts will be selected.

Changes proposed in this PR:

  • Changes the Transformation base class so that apply_to has a return value, which is assumed to be a token containing the right information so it can be passed into a reverse argument in order to undo the transformation.
  • Adds gdp.transform_current_disjunctive_logic transformation

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.

Copy link
Copy Markdown
Contributor

@bernalde bernalde left a comment

Choose a reason for hiding this comment

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

Cool PR. I see how this would simplify things for GDPOpt. I see no issues with it.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 17, 2023

Codecov Report

Patch coverage: 95.10% and project coverage change: -0.20% ⚠️

Comparison is base (3b3b7e7) 87.83% compared to head (d595a32) 87.63%.
Report is 10 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2809      +/-   ##
==========================================
- Coverage   87.83%   87.63%   -0.20%     
==========================================
  Files         770      771       +1     
  Lines       89642    89996     +354     
==========================================
+ Hits        78733    78872     +139     
- Misses      10909    11124     +215     
Flag Coverage Δ
linux 84.88% <95.10%> (+0.01%) ⬆️
osx 74.61% <95.10%> (+0.02%) ⬆️
other 85.06% <95.10%> (+0.01%) ⬆️
win 82.13% <95.10%> (+0.01%) ⬆️

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

Files Changed Coverage Δ
pyomo/dae/plugins/colloc.py 98.86% <ø> (-0.01%) ⬇️
pyomo/dae/plugins/finitedifference.py 97.74% <ø> (-0.02%) ⬇️
pyomo/gdp/plugins/fix_disjuncts.py 92.15% <ø> (ø)
pyomo/gdp/plugins/gdp_to_mip_transformation.py 99.14% <ø> (ø)
pyomo/gdp/util.py 89.11% <40.00%> (-0.85%) ⬇️
pyomo/core/base/transformation.py 81.25% <85.71%> (+1.25%) ⬆️
...gdp/plugins/transform_current_disjunctive_state.py 99.12% <99.12%> (ø)
pyomo/core/__init__.py 100.00% <100.00%> (ø)
pyomo/core/base/__init__.py 100.00% <100.00%> (ø)
pyomo/gdp/plugins/__init__.py 100.00% <100.00%> (ø)

... and 2 files with indirect coverage changes

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

Copy link
Copy Markdown
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.

Overall, this looks reasonable, but there is a bug that needs to be addressed before merging (supporting transforming partial models)

for disj in true_disjunctions:
reverse_dict[disj] = (disj.indicator_var.fixed, disj.indicator_var.value)
disj.indicator_var.fix(True)
disj.parent_block().reclassify_component_type(disj, Block)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I worry as this reclassifies all Disjuncts in an indexed Disjunct, whether nor not all the disjuncts state is actually determined by the indicator_var (e.g., only transforming part of a model)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

To document: We are currently resolving this by not allowing partial transformation. We will complain if we cannot fully transform any Disjunction in the active tree specified by targets and we will complain if we reclassifying a DisjunctData that is in a container with other active DisjunctDatas that the transformation doesn't touch.

@emma58 emma58 requested a review from jsiirola August 15, 2023 23:20
Comment on lines +276 to +277
parent_block = disj.parent_block()
parent_block.reclassify_component_type(disj, Block)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The only concern I have here is that if an indexed Disjunct has a member that is going to be fixed True (and thus reclassified as a Block) has any peer Disjuncts that are not in a Disjunction that can be transformed, then those disjuncts are also going to be transformed as active Blocks. That is:

m.d = Disjunct([1,2,3], rule=...)
m.e = Disjunct([1,2], rule=...)
m.disj = Disjunction(expr=[m.d[1], m.e[1])
m.d[1].indicator_var = False
TransformationFactory('gdp.transform_current_disjunctive_state').apply_to(m)

...Now m.e[2] is an active block in the model!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this is actually okay--we're comparing the GDP hierarchy with the component hierarchy as we do this transformation, so if there are components that we didn't catch as we transform GDP stuff, we throw an error at the end since we may have inadvertently reclassified them. So your example above throws this error:

Traceback (most recent call last):
  File "/home/esjohn/src/pyomo/thing.py", line 10, in <module>
    TransformationFactory('gdp.transform_current_disjunctive_state').apply_to(m)
  File "/home/esjohn/src/pyomo/pyomo/core/base/transformation.py", line 77, in apply_to
    reverse_token = self._apply_to(model, **kwds)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/esjohn/src/pyomo/pyomo/gdp/plugins/transform_current_disjunctive_state.py", line 146, in _apply_to
    raise GDP_Error(
pyomo.gdp.disjunct.GDP_Error: Found active Disjuncts on the model that were not included in any Disjunctions:
d[3], d[2], e[2]
Please deactivate them or include them in a Disjunction.

@emma58 emma58 merged commit 9a5ef6b into Pyomo:main Aug 23, 2023
@emma58 emma58 deleted the reversible-fix-disjunctive-logic branch August 23, 2023 16:38
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.

5 participants