Skip to content

Commit

Permalink
added to diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
dallan-keylogic committed Jan 31, 2025
1 parent 1bb1470 commit 8e26416
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 17 deletions.
70 changes: 70 additions & 0 deletions idaes/core/util/model_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
deactivated_objectives_set,
variables_in_activated_constraints_set,
variables_not_in_activated_constraints_set,
variables_with_none_value_in_activated_equalities_set,
number_activated_greybox_equalities,
number_deactivated_greybox_equalities,
activated_greybox_block_set,
Expand Down Expand Up @@ -631,6 +632,50 @@ def display_variables_with_none_value(self, stream=None):
footer="=",
)

def display_variables_with_none_value_in_activated_constraints(self, stream=None):
"""
Prints a list of variables with values of None that are present in the
mathematical program generated to solve the model. This list includes only
variables in active constraints that are reachable through active blocks.
Args:
stream: an I/O object to write the list to (default = stdout)
Returns:
None
"""
if stream is None:
stream = sys.stdout

_write_report_section(
stream=stream,
lines_list=[
f"{v.name}"
for v in variables_with_none_value_in_activated_equalities_set(self._model)
],
title=f"The following variable(s) have a value of None:",
header="=",
footer="=",
)

def _verify_active_variables_initialized(self, stream=None):
"""
Validate that all variables are initialized (i.e., have values set to
something other than None) before doing further numerical analysis.
Stream argument provided for forward compatibility (in case we want
to print a list or something).
"""
n_uninit = len(variables_with_none_value_in_activated_equalities_set(self._model))
if n_uninit > 0:
raise RuntimeError(
f"Found {n_uninit} variables with a value of None in the mathematical "
"program generated by the model. They must be initialized with non-None "
"values before numerical analysis can proceed. Run "
+ self.display_variables_with_none_value_in_activated_constraints.__name__
+ " to display a list of these variables."
)

def display_variables_with_value_near_zero(self, stream=None):
"""
Prints a list of variables with a value close to zero. The tolerance
Expand Down Expand Up @@ -928,6 +973,8 @@ def display_overconstrained_set(self, stream=None):

stream.write("=" * MAX_STR_LENGTH + "\n")



def display_variables_with_extreme_jacobians(self, stream=None):
"""
Prints the variables associated with columns in the Jacobian with extreme
Expand All @@ -942,6 +989,8 @@ def display_variables_with_extreme_jacobians(self, stream=None):
None
"""
self._verify_active_variables_initialized(stream=stream)

if stream is None:
stream = sys.stdout

Expand Down Expand Up @@ -977,6 +1026,8 @@ def display_constraints_with_extreme_jacobians(self, stream=None):
None
"""
self._verify_active_variables_initialized(stream=stream)

if stream is None:
stream = sys.stdout

Expand Down Expand Up @@ -1013,6 +1064,8 @@ def display_extreme_jacobian_entries(self, stream=None):
None
"""
self._verify_active_variables_initialized(stream=stream)

if stream is None:
stream = sys.stdout

Expand Down Expand Up @@ -1046,6 +1099,8 @@ def display_near_parallel_constraints(self, stream=None):
None
"""
self._verify_active_variables_initialized(stream=stream)

if stream is None:
stream = sys.stdout

Expand Down Expand Up @@ -1078,6 +1133,8 @@ def display_near_parallel_variables(self, stream=None):
None
"""
self._verify_active_variables_initialized(stream=stream)

if stream is None:
stream = sys.stdout

Expand Down Expand Up @@ -1161,6 +1218,8 @@ def display_constraints_with_mismatched_terms(self, stream=None):
None
"""
self._verify_active_variables_initialized(stream=stream)

if stream is None:
stream = sys.stdout

Expand Down Expand Up @@ -1194,6 +1253,8 @@ def display_constraints_with_canceling_terms(self, stream=None):
None
"""
self._verify_active_variables_initialized(stream=stream)

if stream is None:
stream = sys.stdout

Expand Down Expand Up @@ -1232,6 +1293,8 @@ def display_problematic_constraint_terms(
None
"""
self._verify_active_variables_initialized(stream=stream)

if stream is None:
stream = sys.stdout

Expand Down Expand Up @@ -1321,6 +1384,11 @@ def display_constraints_with_no_free_variables(self, stream=None):
None
"""
# Although, in principle, this method doesn't require
# all variables to be initialized, its current
# implementation does.
self._verify_active_variables_initialized(stream=stream)

if stream is None:
stream = sys.stdout

Expand Down Expand Up @@ -1803,6 +1871,8 @@ def report_numerical_issues(self, stream=None):
None
"""
self._verify_active_variables_initialized(stream=stream)

if stream is None:
stream = sys.stdout
jac, nlp = get_jacobian(self._model, scaled=False)
Expand Down
23 changes: 12 additions & 11 deletions idaes/core/util/model_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1729,37 +1729,38 @@ def number_active_variables_in_deactivated_blocks(block):
"""
return len(active_variables_in_deactivated_blocks_set(block))

def uninitialized_variables_in_activated_equalities_set(block):
def variables_with_none_value_in_activated_equalities_set(block):
"""
Method to return a ComponentSet of all fixed Var components which appear
within an equality Constraint in a model.
Method to return a ComponentSet of all Var components which
have a value of None in the set of activated constraints.
Args:
block : model to be studied
Returns:
A ComponentSet including all fixed Var components which appear within
activated equality Constraints in block
A ComponentSet including all Var components which
have a value of None in the set of activated constraints.
"""
var_set = ComponentSet()
for v in variables_in_activated_equalities_set(block):
if v.value is None:
var_set.add(v)
return var_set

def number_uninitialized_variables_in_activated_equalities(block):
def number_variables_with_none_value_in_activated_equalities(block):
"""
Method to return the number of Var components which appear within an active
Constraint but belong to a deactivated Block in a model.
Method to return the number of Var components which
have a value of None in the set of activated constraints.
Args:
block : model to be studied
Returns:
Number of Var components which belong to a deactivated Block but appear
in an activate Constraint in block
Number of Var components which
have a value of None in the set of activated constraints.
"""
return len(uninitialized_variables_in_activated_equalities_set(block))

return len(variables_with_none_value_in_activated_equalities_set(block))

# -------------------------------------------------------------------------
# Reporting methods
Expand Down
122 changes: 122 additions & 0 deletions idaes/core/util/tests/test_model_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,128 @@ def test_vars_near_zero(model):
for i in near_zero_vars:
assert i is model.v1

class TestVariablesWithNoneValue:
def err_msg(self, j):
return (
f"Found {j} variables with a value of None in the mathematical "
"program generated by the model. They must be initialized with non-None "
"values before numerical analysis can proceed. Run "
+ DiagnosticsToolbox.display_variables_with_none_value_in_activated_constraints.__name__
+ " to display a list of these variables."
)

@pytest.fixture(scope="function")
def model(self):
m = ConcreteModel()
m.u = Var()
m.w = Var(initialize=1)
m.x = Var(initialize=1)
m.y = Var(range(3), initialize=[1, None, 2])
m.z = Var()

m.con_w = Constraint(expr= m.w == 0)
m.con_x = Constraint(expr= m.x == 1)
def rule_con_y(b, i):
return b.y[i] == 3
m.con_y = Constraint(range(3), rule=rule_con_y)
m.con_z = Constraint(expr= m.x + m.z == -1)

m.block1 = Block()
m.block1.a = Var()
m.block1.b = Var(initialize=7)
m.block1.c = Var(initialize=-5)
m.block1.d = Var()

m.block1.con_a = Constraint(expr= m.block1.a**2 + m.block1.b == 1)
m.block1.con_b = Constraint(expr= m.block1.b + m.block1.c == 3)
m.block1.con_c = Constraint(expr= m.block1.c + m.block1.a == -4)
return m
@pytest.mark.unit
def test_verify_active_variables_initialized(self, model):
diag_tbx = DiagnosticsToolbox(model)
with pytest.raises(
RuntimeError,
match=self.err_msg(3),
):
diag_tbx._verify_active_variables_initialized()

model.block1.deactivate()
with pytest.raises(
RuntimeError,
match=self.err_msg(2),
):
diag_tbx._verify_active_variables_initialized()

model.con_y[1].deactivate()
model.con_z.deactivate()
# No uninitialized variables in remaining constraints
diag_tbx._verify_active_variables_initialized()

@pytest.mark.unit
def test_error_handling(self, model):
diag_tbx = DiagnosticsToolbox(model)
methods = [
"display_variables_with_extreme_jacobians",
"display_constraints_with_extreme_jacobians",
"display_extreme_jacobian_entries",
"display_near_parallel_constraints",
"display_near_parallel_variables",
"display_constraints_with_mismatched_terms",
"display_constraints_with_canceling_terms",
# "display_problematic_constraint_terms",
"display_constraints_with_no_free_variables",
"report_numerical_issues",
]
for mthd in methods:
mthd_obj = getattr(diag_tbx, mthd)
with pytest.raises(
RuntimeError,
match=self.err_msg(3),
):
mthd_obj()

@pytest.mark.unit
def test_display_problematic_constraint_terms(self, model):
"""
This method needs to be split in a separate function
because it takes a constraint as an argument.
"""
diag_tbx = DiagnosticsToolbox(model)
with pytest.raises(
RuntimeError,
match=self.err_msg(3),
):
diag_tbx.display_problematic_constraint_terms(
model.con_y[1]
)
# Right now the error message should show whether or not
# a problematic constraint is called.
with pytest.raises(
RuntimeError,
match=self.err_msg(3),
):
diag_tbx.display_problematic_constraint_terms(
model.con_y[0]
)
@pytest.mark.unit
def test_display_variables_with_none_value_in_activated_constraints(self, model):
diag_tbx = DiagnosticsToolbox(model)
stream = StringIO()

diag_tbx.display_variables_with_none_value_in_activated_constraints(
stream=stream,
)

expected = """====================================================================================
The following variable(s) have a value of None:
y[1]
z
block1.a
====================================================================================
"""
assert stream.getvalue() == expected

@pytest.mark.unit
def test_vars_with_none_value(model):
Expand Down
12 changes: 6 additions & 6 deletions idaes/core/util/tests/test_model_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -657,23 +657,23 @@ def rule_con_y(b, i):

active_uninit_set = ComponentSet([m.y[1], m.z, m.block1.a])

assert uninitialized_variables_in_activated_equalities_set(m) == active_uninit_set
assert number_uninitialized_variables_in_activated_equalities(m) == len(active_uninit_set)
assert variables_with_none_value_in_activated_equalities_set(m) == active_uninit_set
assert number_variables_with_none_value_in_activated_equalities(m) == len(active_uninit_set)

m.block1.deactivate()

active_uninit_set = ComponentSet([m.y[1], m.z])

assert uninitialized_variables_in_activated_equalities_set(m) == active_uninit_set
assert number_uninitialized_variables_in_activated_equalities(m) == len(active_uninit_set)
assert variables_with_none_value_in_activated_equalities_set(m) == active_uninit_set
assert number_variables_with_none_value_in_activated_equalities(m) == len(active_uninit_set)

m.block1.activate()
m.con_z.deactivate()

active_uninit_set = ComponentSet([m.y[1], m.block1.a])

assert uninitialized_variables_in_activated_equalities_set(m) == active_uninit_set
assert number_uninitialized_variables_in_activated_equalities(m) == len(active_uninit_set)
assert variables_with_none_value_in_activated_equalities_set(m) == active_uninit_set
assert number_variables_with_none_value_in_activated_equalities(m) == len(active_uninit_set)

# -------------------------------------------------------------------------
# Objective methods
Expand Down

0 comments on commit 8e26416

Please sign in to comment.