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

Add support for Greybox model DOF counitng in degrees_of_freedom function #1512

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

avdudchenko
Copy link
Contributor

@avdudchenko avdudchenko commented Oct 26, 2024

Fixes

The current degrees_of_freedom function does not properly account for GreyBoxModel as a set of constraints, resulting in an incorrect number of DOFs being presented.

Summary/Motivation:

With increased usage of GreyBoxModel (specifically Reaktoro-pse) there is a need to support DOF counting when building IDAES model with GreyBoxModels

Changes proposed in this PR:

-add the capability to add unfixed input and output vars on GreyBox model to ComponentSet produced by unfixed_variables_in_activated_equalities_set
-add a counter for the number of "equalities" in gray box model in number_activated_equalities by adding number_grey_box_equalities method

Counting the number of equalities in GreyBox model:
GrayBox models do not provide traditional Pyomo Constraints that bind inputs to outputs as such, we need to assume how many GrayBox model DOFs there are.

In most cases (at least I can't think of any exceptions) GreyBoxes must be 0DOF in the eyes of Pyomo/IPOPT as each output must be some sort of function of provided inputs, regardless if the internal model in the GreyBox is 0DOF, an optimization, or it exposes internal constraints to solve the problem.
As such, it should be safe to assume that the number of outputs equals the number of equality constraints.

Legal Acknowledgement

By contributing to this software project, I agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the license terms described in the LICENSE.txt file at the top level of this directory.
  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.

@avdudchenko avdudchenko changed the title Add support for Graybox model DOF counitng in degrees_of_freedom function Add support for Greybox model DOF counitng in degrees_of_freedom function Oct 26, 2024
@codecov-commenter
Copy link

codecov-commenter commented Oct 26, 2024

Codecov Report

Attention: Patch coverage is 87.50000% with 12 lines in your changes missing coverage. Please review.

Project coverage is 77.06%. Comparing base (5841433) to head (1bf6ff2).

Files with missing lines Patch % Lines
idaes/core/util/model_statistics.py 87.65% 7 Missing and 3 partials ⚠️
idaes/core/util/model_diagnostics.py 86.66% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1512   +/-   ##
=======================================
  Coverage   77.05%   77.06%           
=======================================
  Files         384      384           
  Lines       62338    62428   +90     
  Branches    10222    10245   +23     
=======================================
+ Hits        48034    48109   +75     
- Misses      11875    11887   +12     
- Partials     2429     2432    +3     

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


🚨 Try these New Features:

) + number_grey_box_equalities(block)


def number_grey_box_equalities(block):
Copy link
Member

Choose a reason for hiding this comment

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

number_of_X is less ambiguous in English.
Can add -> int to indicate return type and, if it's possible, the expected type of block.
In docs: "Method to" is superfluous and at any rate this is a function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


def number_grey_box_equalities(block):
"""
Method to return the number of equality constraints in GreyBox
Copy link
Member

Choose a reason for hiding this comment

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

I would say, "Compute total number of equality constraints for all GreyBox objects in this block."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Member

@andrewlee94 andrewlee94 left a comment

Choose a reason for hiding this comment

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

Thanks for this, it is a case we have not considered and Greybox models are becoming more important. To start with, I had a few suggestions for more tests and modularization of the code.

However, a more significant request would be to add these tools and (and some test cases) into the diagnostics toolbox as well. We should at a minimum check that the DoF and model statistics output (number of variables and constraints) accurately reflect the model with a Greybox present. It might also be nice to add additional sub-rows in the model statistics for greybox components when they are present. Finally, we should look for any edge cases that might occur with the rest of the toolbox and if there are other things we need to consider when using greybox models.

@@ -529,7 +555,7 @@ def deactivated_inequalities_generator(block):
block : model to be studied

Returns:
A generator which returns all indeactivated equality Constraint
A generator which returns all in deactivated equality Constraint
Copy link
Member

Choose a reason for hiding this comment

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

The "in" should be removed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

# Checks for greyboxes, and if they exist will add
# input and output vars to var_set if they are free
# inputs and outputs are defined names for greybox class and should always exist
for grey_box in _iter_indexed_block_data_objects(
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be good to have a number_of_greybox_variables function to do this so that users can check that separately and so that we can test the code in isolation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added one for variables, and unfixed variables.

@@ -685,6 +689,42 @@ def test_degrees_of_freedom(m):
assert degrees_of_freedom(m.b2) == -1


@pytest.mark.unit
Copy link
Member

Choose a reason for hiding this comment

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

We should also have test for the functions to collect the number of greybox variables and constraints.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added tests

@Robbybp
Copy link
Member

Robbybp commented Oct 28, 2024

In most cases (at least I can't think of any exceptions) GreyBoxes must be 0DOF in the eyes of Pyomo/IPOPT as each output must be some sort of function of provided inputs, regardless if the internal model in the GreyBox is 0DOF, an optimization, or it exposes internal constraints to solve the problem.
As such, it should be safe to assume that the number of outputs equals the number of equality constraints.

IIRC, outputs just get translated to equality constraints of the form y = GB(x), but the greybox can define other equality constraints as well. So the number of equality constraints is at least the number of outputs, but can be greater.

@avdudchenko
Copy link
Contributor Author

@Robbybp : Right, so you can include equality constraints in a graybox and send them to the solver (ipopt) - but the variables in those constraints are not actually in pyomo space- and thus we can't count them (hence why here I am only looking at inputs and outputs and ignoring any internal equality constraints). (e.g. looking at this example)

At this point, we just want to look at the graybox as a constraint that binds exposed pyomo inputs to outputs on it, and assume that the users properly setup the internal graybox model to have the correct DOFS... ideally we would have some metadata on the graybox that tells us its internal # of DOFs so we can include it in the total count. But I opt to avoid that for now... unless we want to add support for this now.

Comment on lines 401 to 406
equalities = 0
for grey_box in _iter_indexed_block_data_objects(
block, ctype=ExternalGreyBoxBlock, active=True, descend_into=True
):
equalities += len(grey_box.outputs)
return equalities
Copy link
Member

Choose a reason for hiding this comment

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

As written, this will be incorrect for grey box models that have a nonzero number of "explicitly defined equality constraints" (as opposed to the equality constraints that are created to define the outputs). I think all that's needed to correct this is:

equalities += grey_box.get_external_model().n_equality_constraints()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, did not think about this scenario! Updated to test graybox with suggestion, this now includes an example constraint that binds 2 inputs together.

        def input_names(self):
            return ["a1", "a2", "a3"]

        def output_names(self):
            return ["o1", "o2"]

        def equality_constraint_names(self):
            return ["a12"]

        def evaluate_equality_constraints(self):
            a1 = self._input_values[0]
            a2 = self._input_values[1]
            return [a1 * 0.5 - a2]

@avdudchenko
Copy link
Contributor Author

Thanks for this, it is a case we have not considered and Greybox models are becoming more important. To start with, I had a few suggestions for more tests and modularization of the code.

However, a more significant request would be to add these tools and (and some test cases) into the diagnostics toolbox as well. We should at a minimum check that the DoF and model statistics output (number of variables and constraints) accurately reflect the model with a Greybox present. It might also be nice to add additional sub-rows in the model statistics for greybox components when they are present. Finally, we should look for any edge cases that might occur with the rest of the toolbox and if there are other things we need to consider when using greybox models.

So I started adding display to diagnostics tools

  • the key question is should we include gray box stats with all others (e.g when counting blocks - should gray blocks count to total block count?, same with variables etc?, or should they be separate category only?)

idaes/core/util/model_statistics.py Outdated Show resolved Hide resolved
@lbianchi-lbl lbianchi-lbl added the Priority:High High Priority Issue or PR label Oct 31, 2024
@lbianchi-lbl
Copy link
Contributor

This is just waiting for @avdudchenko to extend the reporting functionality for diagnostics. This should be in the November release.

@avdudchenko
Copy link
Contributor Author

avdudchenko commented Nov 1, 2024

Ready for another round of reviews, I updated model statistics and diagnostics to try and treat greybox models as part of full model,
so variables and equities from greyboxes will be added with other stats as well, in addition to providing additional grey box model outputs via the diagnostic toolbox, which will show this:

    Activated Blocks: 1 (Deactivated: 0)
    Free Variables in Activated Constraints: 4 (External: 0)
        Free Variables with only lower bounds: 0
        Free Variables with only upper bounds: 0
        Free Variables with upper and lower bounds: 0
    Fixed Variables in Activated Constraints: 3 (External: 0)
    Activated Equality Constraints: 5 (Deactivated: 3)
    Activated Inequality Constraints: 0 (Deactivated: 0)
    Activated Objectives: 0 (Deactivated: 0)
    GreyBox Statistics
        Activated GreyBox models: 1 (Deactivated: 1)
        Activated GreyBox Equalities: 3 (Deactivated: 3)
        Free Variables in Activated GreyBox Equalities: 4 (Fixed: 1)

@Robbybp
Copy link
Member

Robbybp commented Nov 1, 2024

I'm curious what happens in the structural analysis when you have a greybox model. I can't imagine the under/overconstrained subsystems are handled correctly. Do you know what happens in this case?

@andrewlee94 andrewlee94 dismissed their stale review November 6, 2024 17:27

Freeing up my review seeing as I am leaving the project (for now at least).

Copy link
Contributor

@dallan-keylogic dallan-keylogic left a comment

Choose a reason for hiding this comment

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

Looks mostly good, just a few issues with docstrings to resolve.

idaes/core/util/model_statistics.py Show resolved Hide resolved
@@ -195,7 +294,11 @@ def number_total_constraints(block):
Returns:
Number of Constraint components in block
"""
return sum(1 for _ in activated_block_component_generator(block, ctype=Constraint))
number_standard_constraints = sum(
Copy link
Contributor

Choose a reason for hiding this comment

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

The docstring of this function needs to be updated to explain what's going on with greybox models.

@@ -280,7 +383,9 @@ def number_deactivated_constraints(block):
Returns:
Number of deactivated Constraint components in block
"""
return sum(1 for _ in deactivated_constraints_generator(block))
standard_equalities = sum(1 for _ in deactivated_constraints_generator(block))
Copy link
Contributor

Choose a reason for hiding this comment

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

The docstring of this function needs to be updated to explain what's going on with greybox models.

@@ -325,7 +430,9 @@ def number_total_equalities(block):
Returns:
Number of equality Constraint components in block
"""
return sum(1 for _ in total_equalities_generator(block))
standard_equalities = sum(1 for _ in total_equalities_generator(block))
greybox_equalities = number_activated_greybox_equalities(block)
Copy link
Contributor

Choose a reason for hiding this comment

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

The docstring of this function needs to be updated to explain what's going on with greybox models.

@@ -377,7 +484,53 @@ def number_activated_equalities(block):
Returns:
Number of activated equality Constraint components in block
"""
return sum(1 for _ in activated_equalities_generator(block))
return sum(
Copy link
Contributor

Choose a reason for hiding this comment

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

The docstring of this function needs to be updated to explain what's going on with greybox models.


def number_activated_greybox_equalities(block) -> int:
"""
Function to compute total number of equality constraints for all GreyBox objects in this block.
Copy link
Contributor

Choose a reason for hiding this comment

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

"total number of equality constraints" -> "total number of implied equality constraints"

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, it might be fine leaving as is. I didn't realize GreyBox models have an n_equality_constraints() attribute. The distinction here is probably that they don't exist as Constraint objects.

A GreyBox model is always assumed to be 0DOFs where each output[i]==f(inputs)
where f is GreyBox model, this should be true regardless if
GreyBox model is doing internal optimization or not, as every output
is calculated through a the GreyBox internal model using provided inputs.
Copy link
Contributor

Choose a reason for hiding this comment

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

"through a the GreyBox" -> "through the GreyBox"

Also, is this 0DOFs property of GreyBox models always true, or is it true only if the user has correctly configured their GreyBox model?

@@ -423,7 +576,9 @@ def number_deactivated_equalities(block):
Returns:
Number of deactivated equality Constraint components in block
"""
return sum(1 for _ in deactivated_equalities_generator(block))
standard_equalities = sum(1 for _ in deactivated_equalities_generator(block))
Copy link
Contributor

Choose a reason for hiding this comment

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

Update docstring to reflect inclusion of Greybox.

Comment on lines 1234 to 1235
A ComponentSet including all unfixed Var components which appear within
activated equality Constraints in block
Copy link
Contributor

Choose a reason for hiding this comment

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

This docstring is incorrect. Also, are greybox_variables Var objects?

assert number_total_equalities(m) == 5
assert number_deactivated_equalities(m) == 3
assert number_deactivated_constraints(m) == 3
report_statistics(m)
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this line test?

@dallan-keylogic
Copy link
Contributor

You also should add a warning to the structural analysis tool that it's not compatible with grey box models, then make sure that report_structural_issues in the diagnostics toolbox has sensible results.

@avdudchenko
Copy link
Contributor Author

You also should add a warning to the structural analysis tool that it's not compatible with grey box models, then make sure that report_structural_issues in the diagnostics toolbox has sensible results.

oof, I have not worked with this tool set deeply enough to even know where to start.

Can we leave this for another PR. At this stage, I doubt many users would try to use diagnostics with Greybox models in general... so it should be okay. I would rather make a new PR after testing and finding all issues with diagnostic tools and then add appropriate doc/warnings.

@Robbybp
Copy link
Member

Robbybp commented Nov 22, 2024

Can we leave this for another PR. At this stage, I doubt many users would try to use diagnostics with Greybox models in general... so it should be okay. I would rather make a new PR after testing and finding all issues with diagnostic tools and then add appropriate doc/warnings.

I agree, but I don't want to confidently give wrong analyses if we're supporting GreyBox in one part of the tool (model statistics). Can you raise a "beta warning" or something in report_structural_issues and report_numerical_issues if greybox blocks are detected?

@Robbybp
Copy link
Member

Robbybp commented Nov 22, 2024

@avdudchenko Can you also open an issue about handling GBBs in structural and numerical diagnostics? If you provide a MWE to test on, I can try to update incidence analysis to do something reasonable.

@avdudchenko
Copy link
Contributor Author

@avdudchenko Can you also open an issue about handling GBBs in structural and numerical diagnostics? If you provide a MWE to test on, I can try to update incidence analysis to do something reasonable.

Will do.

@avdudchenko
Copy link
Contributor Author

Can we leave this for another PR. At this stage, I doubt many users would try to use diagnostics with Greybox models in general... so it should be okay. I would rather make a new PR after testing and finding all issues with diagnostic tools and then add appropriate doc/warnings.

I agree, but I don't want to confidently give wrong analyses if we're supporting GreyBox in one part of the tool (model statistics). Can you raise a "beta warning" or something in report_structural_issues and report_numerical_issues if greybox blocks are detected?

I added error raising for those functions for now.

@dallan-keylogic
Copy link
Contributor

The Exceptions being raised when report_structural_issues and report_numerical_issues are being called are good, but the user could still call the structural analysis tool by itself and run into issues. Why not just have a single exception when creating the DiagnosticsToolbox (and SVDToolbox and DegeneracyHunter) if the model has any grey box models? You'd have to comment out the tests on model statistics, but leave them in so we can use them later. Also add tests making sure the exception is raised when creating any of those objects.

We can sort out which methods support grey box models and which ones don't in another issue/PR and fine tune our warnings/errors later.

@avdudchenko
Copy link
Contributor Author

The Exceptions being raised when report_structural_issues and report_numerical_issues are being called are good, but the user could still call the structural analysis tool by itself and run into issues. Why not just have a single exception when creating the DiagnosticsToolbox (and SVDToolbox and DegeneracyHunter) if the model has any grey box models? You'd have to comment out the tests on model statistics, but leave them in so we can use them later. Also add tests making sure the exception is raised when creating any of those objects.

We can sort out which methods support grey box models and which ones don't in another issue/PR and fine tune our warnings/errors later.

Done, I also did not remove prior tests as they directly call on report function.

idaes/core/util/model_statistics.py Outdated Show resolved Hide resolved
idaes/core/util/model_statistics.py Outdated Show resolved Hide resolved
idaes/core/util/model_statistics.py Outdated Show resolved Hide resolved
idaes/core/util/model_statistics.py Show resolved Hide resolved
idaes/core/util/model_statistics.py Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority:High High Priority Issue or PR WaterTAP
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants