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

Rename SVDToolbox to JacobianAnalysisToolbox #1297

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/explanations/model_diagnostics/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Model Diagnostics Workflow
.. toctree::
:maxdepth: 1

svd_analysis
jacobian_analysis
degeneracy_hunter

Introduction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SVD Analysis
============
Jacobian Analysis
=================

.. contents::
:depth: 3
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Jacobian Analysis Toolbox
=========================

The IDAES Jacobian Analysis Toolbox is an advanced diagnostics tool for helping to identify scaling, degeneracy and ill-conditioning issues in model Jacobians and is accessible through the ``DiagnosticsToolbox``.

Further discussion of how to use and interpret the results of the Jacobian Analysis tools can be found :ref:`here<explanations/model_diagnostics/jacobian_analysis:Jacobian Analysis>`.

.. autoclass:: idaes.core.util.model_diagnostics.JacobianAnalysisToolbox
:members:

SVD Callbacks
-------------

The Jacobian Analysis Toolbox supports callbacks to select the SVD analysis tool to use. Two callbacks are provided to make use of methods available in Scipy.

.. automodule:: idaes.core.util.model_diagnostics
:noindex:
:members: svd_dense, svd_sparse
16 changes: 0 additions & 16 deletions docs/reference_guides/core/util/diagnostics/svd_toolbox.rst

This file was deleted.

4 changes: 2 additions & 2 deletions docs/reference_guides/core/util/model_diagnostics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ The IDAES toolset contains a number of utility functions which can be useful for
:maxdepth: 1

diagnostics/diagnostics_toolbox
diagnostics/svd_toolbox
diagnostics/jacobian_toolbox
diagnostics/degeneracy_hunter
diagnostics/degeneracy_hunter_legacy

Other Methods
^^^^^^^^^^^^^

.. automodule:: idaes.core.util.model_diagnostics
:exclude-members: DegeneracyHunter, DiagnosticsToolbox, SVDToolbox, DegeneracyHunter2, svd_dense, svd_sparse
:exclude-members: DegeneracyHunter, DiagnosticsToolbox, JacobianAnalysisToolbox, DegeneracyHunter2, svd_dense, svd_sparse
:members:

45 changes: 22 additions & 23 deletions idaes/core/util/model_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,15 +277,15 @@
)


SVDCONFIG = ConfigDict()
SVDCONFIG.declare(
JCONFIG = ConfigDict()
JCONFIG.declare(
"number_of_smallest_singular_values",
ConfigValue(
domain=PositiveInt,
description="Number of smallest singular values to compute",
),
)
SVDCONFIG.declare(
JCONFIG.declare(
"svd_callback",
ConfigValue(
default=svd_dense,
Expand All @@ -297,23 +297,23 @@
"return the u, s and v matrices as numpy arrays.",
),
)
SVDCONFIG.declare(
JCONFIG.declare(
"svd_callback_arguments",
ConfigValue(
default=None,
domain=dict,
description="Optional arguments to pass to SVD callback (default = None)",
),
)
SVDCONFIG.declare(
JCONFIG.declare(
"singular_value_tolerance",
ConfigValue(
default=1e-6,
domain=float,
description="Tolerance for defining a small singular value",
),
)
SVDCONFIG.declare(
JCONFIG.declare(
"size_cutoff_in_singular_vector",
ConfigValue(
default=0.1,
Expand Down Expand Up @@ -1330,7 +1330,7 @@
lines_list=next_steps,
title="Suggested next steps:",
line_if_empty=f"If you still have issues converging your model consider:\n"
f"{TAB*2}prepare_svd_toolbox()\n{TAB*2}prepare_degeneracy_hunter()",
f"{TAB*2}prepare_jacobian_analysis_toolbox()\n{TAB*2}prepare_degeneracy_hunter()",
footer="=",
)

Expand Down Expand Up @@ -1379,22 +1379,19 @@
footer="=",
)

@document_kwargs_from_configdict(SVDCONFIG)
def prepare_svd_toolbox(self, **kwargs):
@document_kwargs_from_configdict(JCONFIG)
def prepare_jacobian_analysis_toolbox(self, **kwargs):
"""
Create an instance of the SVDToolbox and store as self.svd_toolbox.

After creating an instance of the toolbox, call
display_underdetermined_variables_and_constraints().
Create an instance of the JacobianAnalysisToolbox and store as self.jac_toolbox.

Returns:

Instance of SVDToolbox
Instance of JacobianAnalysisToolbox

"""
self.svd_toolbox = SVDToolbox(self.model, **kwargs)
self.jac_toolbox = JacobianAnalysisToolbox(self.model, **kwargs)

Check warning on line 1392 in idaes/core/util/model_diagnostics.py

View check run for this annotation

Codecov / codecov/patch

idaes/core/util/model_diagnostics.py#L1392

Added line #L1392 was not covered by tests

return self.svd_toolbox
return self.jac_toolbox

Check warning on line 1394 in idaes/core/util/model_diagnostics.py

View check run for this annotation

Codecov / codecov/patch

idaes/core/util/model_diagnostics.py#L1394

Added line #L1394 was not covered by tests

@document_kwargs_from_configdict(DHCONFIG)
def prepare_degeneracy_hunter(self, **kwargs):
Expand All @@ -1414,18 +1411,18 @@
return self.degeneracy_hunter


@document_kwargs_from_configdict(SVDCONFIG)
class SVDToolbox:
@document_kwargs_from_configdict(JCONFIG)
class JacobianAnalysisToolbox:
"""
Toolbox for performing Singular Value Decomposition on the model Jacobian.
Toolbox for performing analysis the model Jacobian.

Used help() for more information on available methods.

Original code by Doug Allan
Original SVD code by Doug Allan

Args:

model: model to be diagnosed. The SVDToolbox does not support indexed Blocks.
model: model to be diagnosed. The JacobianAnalysisToolbox does not support indexed Blocks.

"""

Expand All @@ -1439,7 +1436,7 @@
)

self._model = model
self.config = SVDCONFIG(kwargs)
self.config = JCONFIG(kwargs)

self.u = None
self.s = None
Expand All @@ -1452,7 +1449,7 @@

if self.jacobian.shape[0] < 2:
raise ValueError(
"Model needs at least 2 equality constraints to perform svd_analysis."
"Model needs at least 2 equality constraints to perform analysis."
)

def run_svd_analysis(self):
Expand Down Expand Up @@ -2093,6 +2090,8 @@
def eq_degenerate(m_dh, v):
# Find the columns with non-zero entries
C = find(J[:, v])[0]
if len(C) == 0:
return Constraint.Skip

Check warning on line 2094 in idaes/core/util/model_diagnostics.py

View check run for this annotation

Codecov / codecov/patch

idaes/core/util/model_diagnostics.py#L2094

Added line #L2094 was not covered by tests
return sum(J[c, v] * m_dh.nu[c] for c in C) == 0

else:
Expand Down
50 changes: 25 additions & 25 deletions idaes/core/util/tests/test_model_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
# Need to update
from idaes.core.util.model_diagnostics import (
DiagnosticsToolbox,
SVDToolbox,
JacobianAnalysisToolbox,
DegeneracyHunter,
DegeneracyHunter2,
svd_dense,
Expand Down Expand Up @@ -1268,11 +1268,11 @@ def test_report_numerical_issues_jacobian(self):
not AmplInterface.available(), reason="pynumero_ASL is not available"
)
@pytest.mark.integration
def test_prepare_svd_toolbox(self, model):
def test_prepare_jacobian_analysis_toolbox(self, model):
dt = DiagnosticsToolbox(model=model.b)
svd = dt.prepare_svd_toolbox()
jac = dt.prepare_jacobian_analysis_toolbox()

assert isinstance(svd, SVDToolbox)
assert isinstance(jac, JacobianAnalysisToolbox)

@pytest.mark.skipif(
not AmplInterface.available(), reason="pynumero_ASL is not available"
Expand All @@ -1296,27 +1296,27 @@ def dummy_callback2(arg1=None, arg2=None):
@pytest.mark.skipif(
not AmplInterface.available(), reason="pynumero_ASL is not available"
)
class TestSVDToolbox:
class TestJacobianAnalysisToolbox:
@pytest.mark.unit
def test_svd_callback_domain(self, dummy_problem):
with pytest.raises(
ValueError,
match="SVD callback must be a callable which takes at least two arguments.",
):
SVDToolbox(dummy_problem, svd_callback="foo")
JacobianAnalysisToolbox(dummy_problem, svd_callback="foo")

with pytest.raises(
ValueError,
match="SVD callback must be a callable which takes at least two arguments.",
):
SVDToolbox(dummy_problem, svd_callback=dummy_callback)
JacobianAnalysisToolbox(dummy_problem, svd_callback=dummy_callback)

svd = SVDToolbox(dummy_problem, svd_callback=dummy_callback2)
svd = JacobianAnalysisToolbox(dummy_problem, svd_callback=dummy_callback2)
assert svd.config.svd_callback is dummy_callback2

@pytest.mark.unit
def test_init(self, dummy_problem):
svd = SVDToolbox(dummy_problem)
svd = JacobianAnalysisToolbox(dummy_problem)

assert svd._model is dummy_problem
assert svd.u is None
Expand Down Expand Up @@ -1344,13 +1344,13 @@ def test_init_small_model(self):

with pytest.raises(
ValueError,
match="Model needs at least 2 equality constraints to perform svd_analysis.",
match="Model needs at least 2 equality constraints to perform analysis.",
):
svd = SVDToolbox(m)
svd = JacobianAnalysisToolbox(m)

@pytest.mark.unit
def test_run_svd_analysis(self, dummy_problem):
svd = SVDToolbox(dummy_problem)
svd = JacobianAnalysisToolbox(dummy_problem)

assert svd.config.svd_callback is svd_dense

Expand All @@ -1372,7 +1372,7 @@ def test_run_svd_analysis(self, dummy_problem):

@pytest.mark.unit
def test_run_svd_analysis_sparse(self, dummy_problem):
svd = SVDToolbox(dummy_problem, svd_callback=svd_sparse)
svd = JacobianAnalysisToolbox(dummy_problem, svd_callback=svd_sparse)
svd.run_svd_analysis()

# SVD sparse is not consistent with signs - manually iterate and check abs value
Expand All @@ -1394,7 +1394,7 @@ def test_run_svd_analysis_sparse(self, dummy_problem):

@pytest.mark.unit
def test_run_svd_analysis_sparse_limit(self, dummy_problem):
svd = SVDToolbox(
svd = JacobianAnalysisToolbox(
dummy_problem, svd_callback=svd_sparse, number_of_smallest_singular_values=2
)
svd.run_svd_analysis()
Expand All @@ -1418,7 +1418,7 @@ def test_run_svd_analysis_sparse_limit(self, dummy_problem):

@pytest.mark.unit
def test_display_rank_of_equality_constraints(self, dummy_problem):
svd = SVDToolbox(dummy_problem)
svd = JacobianAnalysisToolbox(dummy_problem)

stream = StringIO()
svd.display_rank_of_equality_constraints(stream=stream)
Expand All @@ -1434,7 +1434,7 @@ def test_display_rank_of_equality_constraints(self, dummy_problem):

@pytest.mark.unit
def test_display_rank_of_equality_constraints(self, dummy_problem):
svd = SVDToolbox(dummy_problem, singular_value_tolerance=1)
svd = JacobianAnalysisToolbox(dummy_problem, singular_value_tolerance=1)

stream = StringIO()
svd.display_rank_of_equality_constraints(stream=stream)
Expand All @@ -1450,7 +1450,7 @@ def test_display_rank_of_equality_constraints(self, dummy_problem):

@pytest.mark.unit
def test_display_underdetermined_variables_and_constraints(self, dummy_problem):
svd = SVDToolbox(dummy_problem)
svd = JacobianAnalysisToolbox(dummy_problem)

stream = StringIO()
svd.display_underdetermined_variables_and_constraints(stream=stream)
Expand Down Expand Up @@ -1507,7 +1507,7 @@ def test_display_underdetermined_variables_and_constraints(self, dummy_problem):
def test_display_underdetermined_variables_and_constraints_specific(
self, dummy_problem
):
svd = SVDToolbox(dummy_problem)
svd = JacobianAnalysisToolbox(dummy_problem)

stream = StringIO()
svd.display_underdetermined_variables_and_constraints(
Expand All @@ -1534,7 +1534,7 @@ def test_display_underdetermined_variables_and_constraints_specific(

@pytest.mark.unit
def test_display_underdetermined_variables_and_constraints(self, dummy_problem):
svd = SVDToolbox(dummy_problem, size_cutoff_in_singular_vector=1)
svd = JacobianAnalysisToolbox(dummy_problem, size_cutoff_in_singular_vector=1)

stream = StringIO()
svd.display_underdetermined_variables_and_constraints(stream=stream)
Expand Down Expand Up @@ -1590,7 +1590,7 @@ def test_display_constraints_including_variable(self):
m.c3 = Constraint(expr=5 * m.v[3] + 6 * m.v[4] == 30)
m.c4 = Constraint(expr=7 * m.v[4] + 8 * m.v[1] == 40)

svd = SVDToolbox(m)
svd = JacobianAnalysisToolbox(m)

stream = StringIO()
svd.display_constraints_including_variable(variable=m.v[1], stream=stream)
Expand All @@ -1617,7 +1617,7 @@ def test_display_constraints_including_variable_invalid(self):
m.c3 = Constraint(expr=5 * m.v[3] + 6 * m.v[4] == 30)
m.c4 = Constraint(expr=7 * m.v[4] + 8 * m.v[1] == 40)

svd = SVDToolbox(m)
svd = JacobianAnalysisToolbox(m)

with pytest.raises(
TypeError,
Expand All @@ -1639,7 +1639,7 @@ def test_display_constraints_including_variable_not_in_model(self):
m.c3 = Constraint(expr=5 * m.v[3] + 6 * m.v[4] == 30)
m.c4 = Constraint(expr=7 * m.v[4] + 8 * m.v[1] == 40)

svd = SVDToolbox(m)
svd = JacobianAnalysisToolbox(m)

with pytest.raises(AttributeError, match="Could not find y in model."):
svd.display_constraints_including_variable(variable=m2.y)
Expand All @@ -1655,7 +1655,7 @@ def test_display_variables_in_constraint(self):
m.c3 = Constraint(expr=5 * m.v[3] + 6 * m.v[4] == 30)
m.c4 = Constraint(expr=7 * m.v[4] + 8 * m.v[1] == 40)

svd = SVDToolbox(m)
svd = JacobianAnalysisToolbox(m)

stream = StringIO()
svd.display_variables_in_constraint(constraint=m.c1, stream=stream)
Expand All @@ -1682,7 +1682,7 @@ def test_display_variables_in_constraint_invalid(self):
m.c3 = Constraint(expr=5 * m.v[3] + 6 * m.v[4] == 30)
m.c4 = Constraint(expr=7 * m.v[4] + 8 * m.v[1] == 40)

svd = SVDToolbox(m)
svd = JacobianAnalysisToolbox(m)

with pytest.raises(
TypeError,
Expand All @@ -1704,7 +1704,7 @@ def test_display_variables_in_constraint_no_in_model(self):

c6 = Constraint(expr=m.v[1] == m.v[2])

svd = SVDToolbox(m)
svd = JacobianAnalysisToolbox(m)

with pytest.raises(
AttributeError, match="Could not find AbstractScalarConstraint in model."
Expand Down
Loading