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

Diagnostics tool for ill-posed constraints #1454

Merged
merged 62 commits into from
Nov 5, 2024
Merged
Changes from 1 commit
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
37e0fae
Working on cancellation detection
Jul 22, 2024
1664593
Merge branch 'main' of https://github.com/IDAES/idaes-pse into cons_m…
Jul 22, 2024
4d48741
Fixing typo
Jul 22, 2024
a1e91d2
Merge branch 'main' of https://github.com/IDAES/idaes-pse into cons_m…
Aug 6, 2024
33a4435
Improving efficiency
Aug 6, 2024
747eb7d
More efficiency and testing
Aug 6, 2024
707de1e
Reducing duplicated code in tests
Aug 9, 2024
3a6400b
Finishing testing
Aug 9, 2024
c3e315c
Adding additioanl tests and clean up
Aug 9, 2024
2ab55ca
Running pylint
Aug 9, 2024
2af9478
Adding constraint walker to diagnostics toolbox
Aug 9, 2024
b9d12ea
Merge branch 'main' into cons_mag_diagnostics
Aug 23, 2024
162f631
Addressing simple review comments
Aug 23, 2024
90d2cd3
Accumulate node rather than str(node)
Aug 23, 2024
152b044
Clean up some tests
Aug 23, 2024
81c1835
Supporting ranged expressions
Aug 23, 2024
849647c
Merge branch 'main' into cons_mag_diagnostics
Aug 23, 2024
bff77d5
Apply suggestions from code review
andrewlee94 Aug 23, 2024
54e87c4
Merge branch 'main' of https://github.com/IDAES/idaes-pse into cons_m…
Aug 25, 2024
11d3f33
Merge branch 'cons_mag_diagnostics' of https://github.com/andrewlee94…
Aug 25, 2024
85d1956
Regorganising and fixing pylint issues
Aug 25, 2024
d486e85
Fixing false positives for linking constraints
Aug 25, 2024
1048b32
Fixing unused variable warning
Aug 25, 2024
d10a493
Fixing bug related to external functions with string arguments
Sep 6, 2024
00a3434
Merge branch 'main' of https://github.com/IDAES/idaes-pse into cons_m…
Sep 10, 2024
d27de22
Merge branch 'old_scaling_bug' into cons_mag_diagnostics
Sep 10, 2024
6d6fe2c
Addressing comments
Sep 10, 2024
9dbd521
Merge branch 'main' into cons_mag_diagnostics
Sep 11, 2024
678caf8
removing debugging prints
Sep 11, 2024
47a58f4
Merge branch 'main' of https://github.com/IDAES/idaes-pse into cons_m…
Sep 23, 2024
1b5d1d2
Addressing more comments
Sep 23, 2024
18a6ff1
Merge branch 'cons_mag_diagnostics' of https://github.com/andrewlee94…
Sep 23, 2024
ef2e228
Debugging issue with nested external functions
Sep 24, 2024
70636cb
Adding todo
Sep 24, 2024
5d5da12
Relax test tolerance due to Windows failures
Sep 24, 2024
c173984
Adding catches for string arguments/values
Sep 30, 2024
7c03467
Test for nested external functions
Sep 30, 2024
21cca53
Fixing bug in walker logic for string arguments to external funcitons
Oct 8, 2024
ad6dd99
Merge branch 'main' into cons_mag_diagnostics
Oct 9, 2024
a3cde35
Merge branch 'main' into cons_mag_diagnostics
Oct 25, 2024
ddc8fb8
Address double counting of constraints
Oct 25, 2024
bd331e8
Fixing typo
Oct 25, 2024
b3098e1
Bumping Pyomo version
Oct 25, 2024
60e71cb
Adding zero tolerance
Oct 25, 2024
aebb118
Adding test for zero tolerance
Oct 25, 2024
9956286
Fixing typo
Oct 28, 2024
8cf3bbe
Logic to skip scaled equality constraints
Oct 28, 2024
34a2b58
Merge branch 'main' into cons_mag_diagnostics_2
Oct 28, 2024
93ef9d8
Handling mole fraction type constraints
Oct 28, 2024
3284d3e
Merge branch 'main' into cons_mag_diagnostics
Oct 29, 2024
2d5ec93
Catching negations
Oct 29, 2024
5ffec52
Fixing typos
Oct 29, 2024
161b15b
Limit of cancelling combinations
Oct 30, 2024
7deb640
Fixing typos
Oct 30, 2024
86bd61c
Displaying cancelling terms to user
Oct 30, 2024
9b13cb1
More detail of mismatches
Oct 30, 2024
6577481
Adding hooks and warnings about new config arguments
Oct 30, 2024
94fe8c0
Expression aware to string method
Nov 1, 2024
360bb39
Moving expression writer to util/misc
Nov 1, 2024
1fb2743
Typos and comments
Nov 1, 2024
628f445
Using StrEnum
Nov 5, 2024
1a4a1ee
Merge branch 'main' into cons_mag_diagnostics
Nov 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Regorganising and fixing pylint issues
  • Loading branch information
Andrew Lee committed Aug 25, 2024
commit 85d1956d3d948bee8c1175c85979a9d8d95eb2b0
142 changes: 71 additions & 71 deletions idaes/core/util/model_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

__author__ = "Alexander Dowling, Douglas Allan, Andrew Lee, Robby Parker, Ben Knueven"

import math
from operator import itemgetter
import sys
from inspect import signature
Expand Down Expand Up @@ -4313,67 +4312,14 @@ def _check_base_type(self, node):
def _get_value_for_sum_subexpression(self, child_data):
return sum(i for i in child_data[0])

def _check_sum_expression(self, node, child_data):
# Sum expressions need special handling
# For sums, collect all child values into a list
vals = []
# We will check for cancellation in this node at the next level
# Pyomo is generally good at simplifying compound sums
const = True
# Collect data from child nodes
for d in child_data:
vals.append(self._get_value_for_sum_subexpression(d))

# Expression is not constant if any child is not constant
if not d[1]:
const = False

# Check for mismatched terms
if len(vals) > 1:
absvals = [abs(v) for v in vals]
vl = max(absvals)
vs = min(absvals)
if vl != vs:
if vs == 0:
diff = log10(vl)
else:
diff = log10(vl / vs)

if diff >= self._log_mm_tol:
self.mismatched_terms.add(node)

return vals, const

def _check_general_expr(self, node, child_data):
const = self._perform_checks(node, child_data)

try:
val = node._apply_operation(
list(map(self._get_value_for_sum_subexpression, child_data))
)
except ValueError:
raise ValueError(
f"Error in ConstraintTermAnalysisVisitor: error evaluating {str(node)}."
)
except ZeroDivisionError:
raise ZeroDivisionError(
f"Error in ConstraintTermAnalysisVisitor: found division with denominator of 0 "
f"({str(node)})."
)

return [val], const

def _check_other_expression(self, node, child_data):
const = self._perform_checks(node, child_data)

# First, need to get value of input terms, which may be sub-expressions
input_mag = [self._get_value_for_sum_subexpression(i) for i in child_data]

# Next, create a copy of the function with expected magnitudes as inputs
newfunc = node.create_node_with_local_data(input_mag)
def _sum_combinations(self, values_list):
sums = []
for i in chain.from_iterable(
combinations(values_list, r) for r in range(2, len(values_list) + 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

2 is a magic number! Why 2?

Copy link
Member Author

Choose a reason for hiding this comment

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

We are looking for any combination of terms which cancel, thus the minimum number of terms to consider is 2 (a single term cannot cancel with itself). I can add a comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

What this block of code does is go through combinations of terms in an expression first in groups of 2, then in groups of 3, all the way up to groups of len(values_list). So if we have the sum expression a+b+c+d, we first iterate through (a, b), (a, c), (a, d), (b, c), (b, d), (c, d), then iterate through (a, b, c,), (a, b, d), (a, c, d), (b, c, d), then (a,b,c,d).

However, the number of terms you're checking grows exponentially in expression size. In particular, if len(values_list) == m, you'll be checking 2 ** m - m -1 terms. I expect that this sort of check will take an extremely long time on any model with Expressions of any significant length, much less an extreme chonker like eNRTL.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can probably make this more efficient by:

  1. Stripping any term with a value of 0
  2. Breaking the loop at the first failure - we do not count how many cancellations there are, just if there is at least 1.

):
sums.append(abs(sum(i)))

# Evaluate new function and return the value along with check results
return [value(newfunc)], const
return sums

def _perform_checks(self, node, child_data):
# Perform checks for problematic expressions
Expand Down Expand Up @@ -4429,7 +4375,39 @@ def _check_equality_expression(self, node, child_data):

return vals, const

def _check_ranged(self, node, child_data):
def _check_general_expr(self, node, child_data):
const = self._perform_checks(node, child_data)

try:
# pylint: disable-next=protected-access
val = node._apply_operation(
list(map(self._get_value_for_sum_subexpression, child_data))
)
except ValueError:
raise ValueError(
f"Error in ConstraintTermAnalysisVisitor: error evaluating {str(node)}."
)
except ZeroDivisionError:
raise ZeroDivisionError(
f"Error in ConstraintTermAnalysisVisitor: found division with denominator of 0 "
f"({str(node)})."
)
mrmundt marked this conversation as resolved.
Show resolved Hide resolved

return [val], const

def _check_other_expression(self, node, child_data):
const = self._perform_checks(node, child_data)

# First, need to get value of input terms, which may be sub-expressions
input_mag = [self._get_value_for_sum_subexpression(i) for i in child_data]

# Next, create a copy of the function with expected magnitudes as inputs
newfunc = node.create_node_with_local_data(input_mag)

# Evaluate new function and return the value along with check results
return [value(newfunc)], const

def _check_ranged_expression(self, node, child_data):
lhs_vals, lhs_const = self._check_equality_expression(node, child_data[:2])
rhs_vals, rhs_const = self._check_equality_expression(node, child_data[1:])

Expand All @@ -4443,19 +4421,41 @@ def _check_ranged(self, node, child_data):

return vals, const

def _sum_combinations(self, values_list):
sums = []
for i in chain.from_iterable(
combinations(values_list, r) for r in range(2, len(values_list) + 1)
):
sums.append(abs(sum(i)))
def _check_sum_expression(self, node, child_data):
# Sum expressions need special handling
# For sums, collect all child values into a list
vals = []
# We will check for cancellation in this node at the next level
# Pyomo is generally good at simplifying compound sums
const = True
# Collect data from child nodes
for d in child_data:
vals.append(self._get_value_for_sum_subexpression(d))

return sums
# Expression is not constant if any child is not constant
if not d[1]:
const = False

# Check for mismatched terms
if len(vals) > 1:
absvals = [abs(v) for v in vals]
vl = max(absvals)
vs = min(absvals)
if vl != vs:
if vs == 0:
diff = log10(vl)
else:
diff = log10(vl / vs)

if diff >= self._log_mm_tol:
self.mismatched_terms.add(node)

return vals, const

node_type_method_map = {
EXPR.EqualityExpression: _check_equality_expression,
EXPR.InequalityExpression: _check_equality_expression,
EXPR.RangedExpression: _check_ranged,
EXPR.RangedExpression: _check_ranged_expression,
EXPR.SumExpression: _check_sum_expression,
EXPR.NPV_SumExpression: _check_sum_expression,
EXPR.ProductExpression: _check_general_expr,
Expand All @@ -4481,7 +4481,7 @@ def exitNode(self, node, data):
"""
Method to call when exiting node to check for potential issues.
"""
# Return [node values], [constant
# Return [node values], constant
# first check if the node is a leaf
nodetype = type(node)

Expand Down