8000 pyomo.doe adding more verbose output for sensitivity analysis by smondal13 · Pull Request #3525 · Pyomo/pyomo · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

pyomo.doe adding more verbose output for sensitivity analysis #3525

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

Open
wants to merge 51 commits into
base: main
Choose a base branch
from

Conversation

smondal13
Copy link
@smondal13 smondal13 commented Mar 19, 2025

Fixes # .

Summary/Motivation:

  • Adding more verbose is helpful to analyze the problem
  • Adding a new method to check the FIM in the utils.py can help in the general usage of the method.
  • Adding a new standalone function in utils.py to calculate different metrics of the FIM is helpful in case the user does not want to build the get_labeled_model().
  • Adding arguments in the reactor_example.py will help with the testing.

Changes proposed in this PR:

  • added eigenvalues, determinant of FIM, and trace of FIM for sensitivity output in compute_FIM_full_factorial()
  • added check_FIM() in utils.py by separating it from check_model_FIM() in doe.py
  • added compute_FIM_metrics() in utils.py
  • added get_FIM_metrics() in utils.py
  • added arguments in the reactor_example.py

Legal Acknowledgement

By contributing to this software project, I have read the contribution guide and agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the BSD license.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

@smondal13 smondal13 marked this pull request as draft March 19, 2025 14:13
@smondal13
Copy link
Author
< 8000 /tr>

@adowling2 @djlaky ready for early feedback

@adowling2
Copy link
Member

Next step:

  • Also add solver status

@adowling2
Copy link
Member

@smondal13 See #3532 for an example of what @jsiirola is suggesting

… doe object that uses the "reactor_experiment" example in doe directory. Also added the png files to gitignore that are stored when "reactor_example.py" is run.
@smondal13 smondal13 force-pushed the adding_eigen_values branch from 6e68c2b to e9e83b6 Compare April 30, 2025 01:17
@adowling2 adowling2 moved this from Todo to Development in ParmEst & Pyomo.DoE Development Apr 30, 2025
@blnicho
Copy link
Member
blnicho commented Jun 3, 2025

@smondal13 could you run black on your code? The linter is not happy with a couple of your import statements.

See: https://pyomo.readthedocs.io/en/latest/contribution_guide.html#coding-standards

8000
Comment on lines 1511 to 1522
fim_factorial_results: a dictionary of the results with the following keys and
their corresponding values as a list. Each element in the list corresponds to
a different design point in the full factorial space.
"log10 D-opt": list of log10(D-optimality)
"log10 A-opt": list of log10(A-optimality)
"log10 E-opt": list of log10(E-optimality)
"log10 ME-opt": list of log10(ME-optimality)
"eigval_min": list of minimum eigenvalues
"eigval_max": list of maximum eigenvalues
"det_FIM": list of determinants
"trace_FIM": list of traces
"solve_time": list of solve times
Copy link
Member

Choose a reason for hiding this comment

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

This docstring doesn't render nicely. See: https://pyomo--3525.org.readthedocs.build/en/3525/api/pyomo.contrib.doe.doe.DesignOfExperiments.html#pyomo.contrib.doe.doe.DesignOfExperiments.compute_FIM_full_factorial

Try experimenting with the formatting. Maybe if you indent lines 1512-1522 it will preserve the alignment you have in the docstring.

Copy link
Author
@smondal13 smondal13 Jun 12, 2025

Choose a reason for hiding this comment

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

I have run black, and the file was not changed. I have changed some formatted string manually which black did not catch. Hope this solves this issue

Also, I have reformatted the docstring. I think now it should format nicely.

Comment on lines 33 to 49
# expected results
det_expected = np.linalg.det(FIM)
D_opt_expected = np.log10(det_expected)

trace_expected = np.trace(FIM)
A_opt_expected = np.log10(trace_expected)

E_vals_expected, E_vecs_expected = np.linalg.eig(FIM)
min_eigval = np.min(E_vals_expected.real)
if min_eigval <= 0:
E_opt_expected = np.nan
else:
E_opt_expected = np.log10(min_eigval)

cond_expected = np.linalg.cond(FIM)

ME_opt_expected = np.log10(cond_expected)
Copy link
Member

Choose a reason for hiding this comment

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

You typically want to hard-code the expected results in the test instead of copying the function code here. This isn't really testing anything except that the code is the same in both places. If the numpy behavior ever changed resulting in different values, this test would not catch it and would always pass.

Copy link
Author

Choose a reason for hiding this comment

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

I have now hardcoded the values and the arrays.



class TestFIMWarning(unittest.TestCase):
def test_FIM_eigenvalue_warning(self):
Copy link
Member

Choose a reason for hiding this comment

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

I think this looks good.

self.assertEqual(ME_opt, ME_opt_expected)


class TestFIMWarning(unittest.TestCase):
Copy link
Member

Choose a reason for hiding this comment

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

You should include this as another test under the existing class. No need to have separate classes.

Suggested change
class TestFIMWarning(unittest.TestCase):

Copy link
Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 1400 to 1401
@staticmethod def _check_FIM(FIM):
Copy link
Member

Choose a reason for hiding this comment

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

Why did you add this as a staticmethod instead of a utility function outside of the class?

Copy link
Member

Choose a reason for hiding this comment

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

@blnicho Moving this to utils.py is a really good suggestion

Copy link
Author

Choose a reason for hiding this comment

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

All the standalone functions are moved to utils.py

Comment on lines 2363 to 2365
# loggers for the functions
function_logger = logging.getLogger(__name__)
function_logger.setLevel(logging.WARNING)
Copy link
Member

Choose a reason for hiding this comment

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

The logger for this module is retrieved above, with the logging level set based on the user input. See:

# Revtrieve logger and set logging level
self.logger = logging.getLogger(__name__)
self.logger.setLevel(level=logger_level)

I'm worried that grabbing the logger for this module again and setting the level here will lead to weird edge cases with the logging code in the class. What's the reasoning for manually setting the level here?

@adowling2 would you be opposed to moving these stand-alone functions into pyomo.contrib.doe.utils?

Copy link
Member

Choose a reason for hiding this comment

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

If you move these functions into pyomo.contrib.doe.utils, then I would also suggest combining the test files in this PR into a single test_utils.py file.

Copy link
Member

Choose a reason for hiding this comment

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

Great suggestions

Copy link
Author
@smondal13 smondal13 Jun 12, 2025

Choose a reason for hiding this comment

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

Moved the standalone functions to utils.py and compiled all the test files into test_utils.py

@smondal13
Copy link
Author

@blnicho I have implemented your suggestions. It is ready for review now.

…reactor_example.py file to have user defined computation. Changed the docstring formatting in utils.py
< AD73 input type="hidden" name="dropdown_direction" value="w" autocomplete="off" data-targets="batch-deferred-content.inputs" />
… version looks fine. Also, changed the import of run_reactor_doe in test_doe_solve.py
@blnicho blnicho moved this from Todo to Review In Progress in July 2025 Release Jun 23, 2025
Copy link
Member
@adowling2 adowling2 left a comment

Choose a reason for hiding this comment

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

My memory (which may be incorrect) is that @smondal13 ran black without the correct inputs and it changed the string formatting from single quote to double quote. I see this change around line 106 in test_doe_solve.py and a few other places.

Does this matter or is it okay? @blnicho @jsiirola @mrmundt

@blnicho
Copy link
Member
blnicho commented Jun 24, 2025

My memory (which may be incorrect) is that @smondal13 ran black without the correct inputs and it changed the string formatting from single quote to double quote. I see this change around line 106 in test_doe_solve.py and a few other places.

Does this matter or is it okay? @blnicho @jsiirola @mrmundt

It only touches a handful of files in Pyomo.DoE so I think we decided it's fine to leave it. Just make sure to use the black options we talked about for future development.

@@ -11,6 +11,7 @@
import json
import logging
import os.path
import idaes.core.solvers.get_solver
Copy link
Member

Choose a reason for hiding this comment

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

What is this import being used for? We generally want to avoid importing anything from IDAES in Pyomo since it creates a circular dependency.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Review In Progress
Status: Ready for final review
Development

Successfully merging this pull request may close these issues.

6 participants
0