diff --git a/idaes/core/util/model_diagnostics.py b/idaes/core/util/model_diagnostics.py index b2fb42700f..04f7fb3ba8 100644 --- a/idaes/core/util/model_diagnostics.py +++ b/idaes/core/util/model_diagnostics.py @@ -18,7 +18,6 @@ __author__ = "Alexander Dowling, Douglas Allan, Andrew Lee, Robby Parker, Ben Knueven" from operator import itemgetter -import os import sys from inspect import signature from math import log, isclose, inf, isfinite @@ -77,7 +76,6 @@ from pyomo.common.deprecation import deprecation_warning from pyomo.common.errors import PyomoException from pyomo.common.tempfiles import TempfileManager -from pyomo.common.fileutils import find_library from idaes.core.util.model_statistics import ( activated_blocks_set, @@ -441,23 +439,27 @@ def __init__(self, model: _BlockData, **kwargs): self._model = model self.config = CONFIG(kwargs) - # There appears to be a bug in the ASL which causes terminal failures - # if you try to create multiple ASL structs with different external - # functions in the same process. This causes pytest to crash during testing. - # To avoid this, register all known external functions before we call - # PyNumero. - ext_funcs = ["cubic_roots", "general_helmholtz_external", "functions"] - library_set = set() - libraries = [] - - for f in ext_funcs: - library = find_library(f) - if library not in library_set: - library_set.add(library) - libraries.append(library) - - lib_str = "\n".join(libraries) - os.environ["AMPLFUNC"] = lib_str + # # There appears to be a bug in the ASL which causes terminal failures + # # if you try to create multiple ASL structs with different external + # # functions in the same process. This causes pytest to crash during testing. + # # To avoid this, register all known external functions before we call + # # PyNumero. + # ext_funcs = ["cubic_roots", "general_helmholtz_external", "functions"] + # library_set = set() + # libraries = [] + # + # for f in ext_funcs: + # library = find_library(f) + # if library not in library_set: + # library_set.add(library) + # libraries.append(library) + # + # if "AMPLFUNC" in os.environ: + # env_str = "\n".join([os.environ["AMPLFUNC"], *libraries]) + # else: + # env_str = "\n".join(libraries) + # + # os.environ["AMPLFUNC"] = env_str @property def model(self): @@ -1430,9 +1432,17 @@ def report_numerical_issues(self, stream=None): cautions = self._collect_numerical_cautions(jac=jac, nlp=nlp) stats = [] - stats.append( - f"Jacobian Condition Number: {jacobian_cond(jac=jac, scaled=False):.3E}" - ) + try: + stats.append( + f"Jacobian Condition Number: {jacobian_cond(jac=jac, scaled=False):.3E}" + ) + except RuntimeError as err: + if "Factor is exactly singular" in str(err): + _log.info(err) + stats.append(f"Jacobian Condition Number: Undefined (Exactly Singular)") + else: + raise + _write_report_section( stream=stream, lines_list=stats, title="Model Statistics", header="=" ) diff --git a/idaes/core/util/scaling.py b/idaes/core/util/scaling.py index c0ec998ac7..519801163a 100644 --- a/idaes/core/util/scaling.py +++ b/idaes/core/util/scaling.py @@ -28,6 +28,7 @@ __author__ = "John Eslick, Tim Bartholomew, Robert Parker, Andrew Lee" import math +import os import sys import scipy.sparse.linalg as spla @@ -49,12 +50,36 @@ from pyomo.core import expr as EXPR from pyomo.common.numeric_types import native_types from pyomo.core.base.units_container import _PyomoUnit +from pyomo.common.fileutils import find_library import idaes.logger as idaeslog _log = idaeslog.getLogger(__name__) +# There appears to be a bug in the ASL which causes terminal failures +# if you try to create multiple ASL structs with different external +# functions in the same process. This causes pytest to crash during testing. +# To avoid this, register all known external functions before we call +# PyNumero. +ext_funcs = ["cubic_roots", "general_helmholtz_external", "functions"] +library_set = set() +libraries = [] + +for f in ext_funcs: + library = find_library(f) + if library not in library_set: + library_set.add(library) + libraries.append(library) + +if "AMPLFUNC" in os.environ: + env_str = "\n".join([os.environ["AMPLFUNC"], *libraries]) +else: + env_str = "\n".join(libraries) + +os.environ["AMPLFUNC"] = env_str + + def __none_left_mult(x, y): """PRIVATE FUNCTION, If x is None return None, else return x * y""" if x is not None: diff --git a/idaes/core/util/tests/test_model_diagnostics.py b/idaes/core/util/tests/test_model_diagnostics.py index 16cc4de6fb..c7d60acde8 100644 --- a/idaes/core/util/tests/test_model_diagnostics.py +++ b/idaes/core/util/tests/test_model_diagnostics.py @@ -33,6 +33,7 @@ acos, sqrt, Objective, + PositiveIntegers, Set, SolverFactory, Suffix, @@ -1342,6 +1343,46 @@ def test_report_numerical_issues_ok(self): prepare_degeneracy_hunter() prepare_svd_toolbox() +==================================================================================== +""" + + assert stream.getvalue() == expected + + @pytest.mark.component + def test_report_numerical_issues_exactly_singular(self): + m = ConcreteModel() + m.x = Var([1, 2], initialize=1.0) + m.eq = Constraint(PositiveIntegers) + m.eq[1] = m.x[1] * m.x[2] == 1.5 + m.eq[2] = m.x[2] * m.x[1] == 1.5 + m.obj = Objective(expr=m.x[1] ** 2 + 2 * m.x[2] ** 2) + + dt = DiagnosticsToolbox(m) + dt.report_numerical_issues() + + stream = StringIO() + dt.report_numerical_issues(stream) + + expected = """==================================================================================== +Model Statistics + + Jacobian Condition Number: Undefined (Exactly Singular) + +------------------------------------------------------------------------------------ +1 WARNINGS + + WARNING: 2 Constraints with large residuals (>1.0E-05) + +------------------------------------------------------------------------------------ +0 Cautions + + No cautions found! + +------------------------------------------------------------------------------------ +Suggested next steps: + + display_constraints_with_large_residuals() + ==================================================================================== """