Skip to content

Commit

Permalink
Catching negations
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewlee94 committed Oct 29, 2024
1 parent 3284d3e commit 2d5ec93
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 13 deletions.
14 changes: 12 additions & 2 deletions idaes/core/util/model_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -4559,6 +4559,16 @@ def _check_div_expr(self, node, child_data):
# Return the list of values, not constant, is a sum expression (apparent)
return vals, False, True

def _check_negation_expr(self, node, child_data):
# Here we need to defer checking of cancellations too due to different ways
# these can appear in an expression.
# We will simply negate all values on the child node values (child_data[0][0])
# and pass on the rest.
vals = [-i for i in child_data[0][0]]

# Return the list of values, not constant, is a sum expression (apparent)
return vals, child_data[0][1], child_data[0][2]

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

Expand Down Expand Up @@ -4658,8 +4668,8 @@ def _check_sum_expression(self, node, child_data):
EXPR.NPV_DivisionExpression: _check_div_expr,
EXPR.PowExpression: _check_general_expr,
EXPR.NPV_PowExpression: _check_general_expr,
EXPR.NegationExpression: _check_general_expr,
EXPR.NPV_NegationExpression: _check_general_expr,
EXPR.NegationExpression: _check_negation_expr,
EXPR.NPV_NegationExpression: _check_negation_expr,
EXPR.AbsExpression: _check_general_expr,
EXPR.NPV_AbsExpression: _check_general_expr,
EXPR.UnaryFunctionExpression: _check_general_expr,
Expand Down
73 changes: 62 additions & 11 deletions idaes/core/util/tests/test_model_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1348,7 +1348,7 @@ def test_collect_numerical_cautions(self, model):

cautions = dt._collect_numerical_cautions()

assert len(cautions) == 6
assert len(cautions) == 5
assert (
"Caution: 2 Variables with value close to their bounds (abs=1.0E-04, rel=1.0E-04)"
in cautions
Expand All @@ -1359,7 +1359,6 @@ def test_collect_numerical_cautions(self, model):
assert (
"Caution: 1 Variable with extreme value (<1.0E-04 or >1.0E+04)" in cautions
)
assert "Caution: 1 Constraint with potential cancellation of terms" in cautions

@pytest.mark.component
def test_collect_numerical_cautions_jacobian(self):
Expand Down Expand Up @@ -1542,9 +1541,9 @@ def test_report_numerical_issues_ok(self):
No warnings found!
------------------------------------------------------------------------------------
1 Cautions
0 Cautions
Caution: 1 Constraint with potential cancellation of terms
No cautions found!
------------------------------------------------------------------------------------
Suggested next steps:
Expand Down Expand Up @@ -1623,13 +1622,12 @@ def test_report_numerical_issues(self, model):
WARNING: 1 Variable at or outside bounds (tol=0.0E+00)
------------------------------------------------------------------------------------
6 Cautions
5 Cautions
Caution: 2 Variables with value close to their bounds (abs=1.0E-04, rel=1.0E-04)
Caution: 2 Variables with value close to zero (tol=1.0E-08)
Caution: 1 Variable with extreme value (<1.0E-04 or >1.0E+04)
Caution: 1 Variable with None value
Caution: 1 Constraint with potential cancellation of terms
Caution: 1 extreme Jacobian Entry (<1.0E-04 or >1.0E+04)
------------------------------------------------------------------------------------
Expand All @@ -1639,6 +1637,58 @@ def test_report_numerical_issues(self, model):
compute_infeasibility_explanation()
display_variables_at_or_outside_bounds()
====================================================================================
"""

assert stream.getvalue() == expected

@pytest.mark.component
def test_report_numerical_issues_cancellation(self):
model = ConcreteModel()

model.v1 = Var(initialize=1)
model.v2 = Var(initialize=2)
model.v3 = Var(initialize=1e-8)

# Non-problematic constraints
model.c1 = Constraint(expr=2 * model.v1 == model.v2)

# Problematic constraints
model.c10 = Constraint(expr=0 == exp(model.v1 - 0.5 * model.v2))
model.c11 = Constraint(expr=0 == model.v1 - 0.5 * model.v2 + model.v3)

dt = DiagnosticsToolbox(model=model)

stream = StringIO()
dt.report_numerical_issues(stream)

expected = """====================================================================================
Model Statistics
Jacobian Condition Number: Undefined (Exactly Singular)
------------------------------------------------------------------------------------
3 WARNINGS
WARNING: 1 Constraint with large residuals (>1.0E-05)
WARNING: 1 pair of constraints are parallel (to tolerance 1.0E-08)
WARNING: 1 pair of variables are parallel (to tolerance 1.0E-08)
------------------------------------------------------------------------------------
3 Cautions
Caution: 1 Variable with value close to zero (tol=1.0E-08)
Caution: 1 Constraint with mismatched terms
Caution: 1 Constraint with potential cancellation of terms
------------------------------------------------------------------------------------
Suggested next steps:
display_constraints_with_large_residuals()
compute_infeasibility_explanation()
display_near_parallel_constraints()
display_near_parallel_variables()
====================================================================================
"""

Expand Down Expand Up @@ -4656,10 +4706,12 @@ def test_negation_sum_expr(self, func_model):
expr = -(func_model.v1 - func_model.v2)
vv, mm, cc, k = ConstraintTermAnalysisVisitor().walk_expression(expr=expr)

assert vv == [pytest.approx(-0.00001, rel=1e-8)]
# Checking the cancellation should be deferred
# Expect to get two values back
assert vv == [pytest.approx(-1, rel=1e-8), pytest.approx(0.99999, rel=1e-8)]
assert len(mm) == 0
assert expr in cc
assert len(cc) == 1
# Check is deferred, so no cancellations identified
assert len(cc) == 0
assert not k

# acosh has bounds that don't fit with other functions - we will assume we caught enough
Expand Down Expand Up @@ -4766,8 +4818,7 @@ def test_ext_func(self):
assert vv == [pytest.approx(-2.1149075414767577, rel=1e-8)]
assert len(mm) == 0
assert Z in cc
# We actually get two issues here, as one of the input expressions also cancels
assert len(cc) == 2
assert len(cc) == 1
assert not k

# Check that model state did not change
Expand Down

0 comments on commit 2d5ec93

Please sign in to comment.