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

Diagnostic tool to look for potential evaluation errors #1268

Merged
merged 15 commits into from
Nov 15, 2023

Conversation

michaelbynum
Copy link
Contributor

Summary/Motivation:

This PR adds a method to the DiagnosticToolBox that looks for potential evaluation errors such as the log of a negative number. I'm marking it as draft while I work on tests/docs, but I wanted to create the PR to get some initial feedback.

Example:

import idaes
from idaes.core.util.model_diagnostics import DiagnosticsToolbox
import pyomo.environ as pe


m = pe.ConcreteModel()
m.x = pe.Var()
m.y = pe.Var()
m.obj = pe.Objective(expr=m.x**2 + m.y**2)
m.c1 = pe.Constraint(expr=m.y >= pe.log(m.x))
m.c2 = pe.Constraint(expr=m.y >= (m.x - 1)**2.5)
dtb = DiagnosticsToolbox(m)
dtb.report_potential_evaluation_errors()

gives

====================================================================================
Found 2 potential evaluation errors

    c1: Potential log of a negative number in log(x); Argument bounds are (-inf, inf)
    c2: Potential evaluation error in (x - 1)**2.5; base bounds are (-inf, inf); exponent bounds are (2.5, 2.5)

====================================================================================

Note that adding the constraint m.c3 = pe.Constraint(expr=m.x - 1 >= 0) does not change this report because there could still be evaluation errors in infeasible path solvers (like Ipopt). However, if this constraint is added and FBBT is used before checking for evaluation errors, then no warnings will be issued.

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.

@andrewlee94 andrewlee94 mentioned this pull request Sep 12, 2023
31 tasks
@andrewlee94
Copy link
Member

@michaelbynum Just to put down in writing the comments I made at the working group meeting, suggest renaming report_potential_evaluation_errors to display_potential_evaluation_errors to be more consistent with our naming elsewhere, and then adding a check in report_structural_issues to collect the potential evaluation errors and report the number of issues found in the summary (if greater than zero).

@ksbeattie ksbeattie added the Priority:Normal Normal Priority Issue or PR label Sep 14, 2023
Copy link
Member

@Robbybp Robbybp 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 putting this together, @michaelbynum. A few questions on the detection of exponent evaluation errors. Also, do you think that the _EvalErrorWalker should be part of, say, pyomo.util? I guess this PR does not preclude the possibility of "promoting" it eventually if there is a need or use case.

idaes/core/util/model_diagnostics.py Outdated Show resolved Hide resolved
idaes/core/util/model_diagnostics.py Outdated Show resolved Hide resolved
idaes/core/util/model_diagnostics.py Outdated Show resolved Hide resolved
idaes/core/util/model_diagnostics.py Outdated Show resolved Hide resolved
@michaelbynum
Copy link
Contributor Author

@michaelbynum Just to put down in writing the comments I made at the working group meeting, suggest renaming report_potential_evaluation_errors to display_potential_evaluation_errors to be more consistent with our naming elsewhere, and then adding a check in report_structural_issues to collect the potential evaluation errors and report the number of issues found in the summary (if greater than zero).

Done.

@michaelbynum
Copy link
Contributor Author

Working on tests now.

@michaelbynum michaelbynum marked this pull request as ready for review October 30, 2023 19:31
@Robbybp
Copy link
Member

Robbybp commented Oct 30, 2023

Would it be possible or make sense to include an option where bounds are treated as strict inequalities? I.e. I think some modelers would think that log(x); x >= 0 is acceptable if their solver is (strictly) feasible path with respect to this bound.

@michaelbynum
Copy link
Contributor Author

Would it be possible or make sense to include an option where bounds are treated as strict inequalities? I.e. I think some modelers would think that log(x); x >= 0 is acceptable if their solver is (strictly) feasible path with respect to this bound.

Good point. I agree. Should it be an option, or should that just be the behavior?

@michaelbynum
Copy link
Contributor Author

I guess it is solver dependent, so it should probably be an option.

@codecov
Copy link

codecov bot commented Oct 30, 2023

Codecov Report

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

Comparison is base (210db70) 77.30% compared to head (a8c0e0e) 77.33%.

Files Patch % Lines
idaes/core/util/model_diagnostics.py 95.00% 3 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1268      +/-   ##
==========================================
+ Coverage   77.30%   77.33%   +0.02%     
==========================================
  Files         390      390              
  Lines       63249    63367     +118     
  Branches    11625    11648      +23     
==========================================
+ Hits        48893    49003     +110     
- Misses      11837    11843       +6     
- Partials     2519     2521       +2     

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

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.

A couple of (hopefully) small requests from me. There are a few ways we could address the first comment, and we might want to discuss different options to work out the best way to do this.

@@ -1229,6 +1244,10 @@ def report_structural_issues(self, stream=None):
warnings, next_steps = self._collect_structural_warnings()
cautions = self._collect_structural_cautions()

eval_error_warnings, eval_error_cautions = self._collect_potential_eval_errors()
Copy link
Member

Choose a reason for hiding this comment

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

  1. Is there an easy way this could be moved inside the _collect_structural_warnings and _collect_structural_cautions methods? It looks like you did this for efficiency so you only need to walk the model once. However, there is an assert_no_structural_issues method as well (that only checks for warnings) which could not include this as written (although the solution might be to just add this code there as well). I am also working on adding diagnostics to the convergence tester (parameter sweep) tool, and I am looking at using these methods there too (which would mean they need to become public).
  2. Could you add an entry to next_steps as well pointing users to the associated display method.

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 I have addressed both of these now. I realized I was being silly with the cautions. They were completely unnecessary/wrong.

@michaelbynum
Copy link
Contributor Author

@Robbybp, I added an option as you suggested.

@michaelbynum
Copy link
Contributor Author

If tests pass, then this will hopefully be ready to go.

Comment on lines 269 to 276
CONFIG.declare(
"strict_evaluation_error_detection",
ConfigValue(
default=True,
domain=bool,
description="If False, warnings will not be generated for things like log(x) with x >= 0",
),
)
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure what the default should be here. For Ipopt, with default options (i.e. bound_relax_factor=1e-8), we are strictly feasible path with respect to bounds of zero, but not other bounds (at least according to this option's documentation). So something like log(1 - x); x <= 1 could still hit the evaluation error. I think I lean towards treating this as an advanced option that people can turn on if they know what they're doing, although I think we will have to test this in the wild to really know what the "right" default is.

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 tested the example you mentioned with x initialized to 2, and I did not get an evaluation error (if I set the nlp_scaling_method to none).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Which is consistent with the way we do fraction to the boundary in parapint.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That said, I agree with your overall point.

Copy link
Member

Choose a reason for hiding this comment

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

I tested the example you mentioned with x initialized to 2, and I did not get an evaluation error (if I set the nlp_scaling_method to none).

What if you set bound_push to zero (or anything less than 1*bound_relax_factor, I think)?

The point is just that the evaluation error could happen.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I'm suggesting renaming the option, although I'd prefer to get another opinion, as naming preferences can be fairly idiosyncratic.

I would be fine with a name that describes the "region of evaluation error", but would prefer to call it something other than "strict" as this makes me think of how the bounds are handled (unless others think the name makes sense).

Copy link
Member

Choose a reason for hiding this comment

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

Personally I read "strict_evaluation_error_detection" in the sense that @michaelbynum used it, but equally when I read the doc string I asked "what exactly happens when it is set to False and why?" This does have me leaning towards @Robbybp's suggestion as would focus the documentation on "why you might want to set this to relax the definition of bounds".

Copy link
Member

Choose a reason for hiding this comment

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

Since this is a potentially niche use case, maybe we should defer including this option for now (and keep the default behavior of treating bounds as if they are non-strict), then add this later if we find that the output is often dominated by bounds of zero that we don't actually expect to hit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@andrewlee94, are you okay with removing the option for now and adding later?

Copy link
Member

Choose a reason for hiding this comment

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

That is fine by me.

@ksbeattie
Copy link
Member

ksbeattie commented Nov 9, 2023

@Robbybp & @michaelbynum: ping. We're looking at cutting the RC just after Thanksgiving, will this make it?

@michaelbynum
Copy link
Contributor Author

@Robbybp & @michaelbynum: ping. We're looking at cutting the RC just after Thanksgiving, will this make it?

Should make it.

@andrewlee94
Copy link
Member

@michaelbynum Note that you have a few lingering pylint issues too:

idaes/core/util/model_diagnostics.py:59:0: E0611: No name 'generate_standard_repn' in module 'pyomo.repn.standard_repn' (no-name-in-module)
idaes/core/util/model_diagnostics.py:1337:8: W0612: Unused variable 'res' (unused-variable)
idaes/core/util/model_diagnostics.py:1358:4: C0116: Missing function or method docstring (missing-function-docstring)
idaes/core/util/model_diagnostics.py:1840:4: C0116: Missing function or method docstring (missing-function-docstring)
idaes/core/util/model_diagnostics.py:25:0: W0611: Unused Sequence imported from typing (unused-import)
idaes/core/util/model_diagnostics.py:32:0: W0611: Unused is_fixed imported from pyomo.environ (unused-import)

@@ -251,6 +267,14 @@ def svd_sparse(jacobian, number_singular_values):
description="Tolerance for raising a warning for small Jacobian values.",
),
)
CONFIG.declare(
"warn_for_evaluation_error_at_bounds",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Robbybp, @andrewlee94 - what do you think of this name?

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good to me.

Copy link
Member

Choose a reason for hiding this comment

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

Looks good to me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Awesome. Thank you both.

@michaelbynum
Copy link
Contributor Author

I'm not sure why tests are getting skipped...

@andrewlee94
Copy link
Member

@michaelbynum I think it is OK - it is just GitHub getting confused.

@andrewlee94 andrewlee94 merged commit d2f24ad into IDAES:main Nov 15, 2023
40 of 44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority:Normal Normal Priority Issue or PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants