-
Notifications
You must be signed in to change notification settings - Fork 562
Simplify _Q_opt in parmest with block scenario structure. #3789
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
base: main
Are you sure you want to change the base?
Conversation
|
@djlaky @adowling2 Here are my initial thoughts on the redesign. Please provide feedback on code layout to this point. |
|
Dan and I had a good conversation today about the functional things needed in the redesign, and how to go about adding components to the overall block and sub-models. Working to implement changes, closely related to what is done currently in doe's _generate_scenario_blocks. |
adowling2
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice progress
|
@adowling2 @djlaky Added case for bootstrap, now works with theta_est, theta_est_bootstrap, and theta_est_leaveNout if I replace _Q_opt with _Q_opt_blocks. Please review. I am aware of the duplicated code, but when I attempted to unify them using n_scenarios = len(self.exp_list) or len(bootlist), I am getting an error for bootstrap I am still working out. Would we want to fully transition to using theta_est for obj and theta, and cov_est for covariance and add an error/warning? Or should I make it work to calculate cov if calc_cov=True? Thanks! |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3789 +/- ##
==========================================
- Coverage 89.40% 89.31% -0.10%
==========================================
Files 909 909
Lines 105541 105646 +105
==========================================
- Hits 94364 94360 -4
- Misses 11177 11286 +109
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@adowling2 @djlaky Should I tag larger team on this? Please review when available. Thanks! |
…ocks" This reverts commit 1aea99f.
|
@adowling2 @slilonfe5 Ready for review. All functionalities should be working. Two small things to discuss, but relating to covariance calculation in _cov_at_theta. Thank you! |
…_list) cov_est() needs the experiment class to have variables for params, which makes a few tests fail. Was failing tests with one experiment.
adowling2
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sscini Here is my first pass at the review. A handful of places would benefit from more verbose comments to explain the logic or assumptions.
I wonder if there is a better way besides getattr to access variable values. I am thinking how we access values in Pyomo.DoE for variables that are in a suffix.
@slilonfe5, please take a look. I think this would benefit from a conversation with you to integrate the different methods to estimate the parameter covariance.
| model.x2 = Param(initialize=float(data['x2'])) | ||
|
|
||
| # Rate constants | ||
| # @Reviewers: Can we switch this to explicitly defining which parameters are to be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think this example could/should be reworked to fully embrace the Experiment class.
|
|
||
| # Parameter estimation with covariance | ||
| obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=17) | ||
| obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=19) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to satisfy my curiosity, is 17 incorrect and 19 correct? Is this a mistake in the example that is being fixed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the data in the csv file, it should be 19. There are 19 experiments and 4 experiment outputs. The experiment counter (_count_total_experiments) is currently returning 76, which is why I have mentioned making it more robust and commented out the related assertion at this time.
| # Parameter estimation with covariance | ||
| obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=17) | ||
| obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=19) | ||
| print(obj) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While we are editing this, let's print out something such as "print("Least squares objective = ",obj)", etc. to help a new user.
| ) # min^-1 | ||
| model.k3 = pyo.Param( | ||
| initialize=1.0 / 6000.0, within=pyo.PositiveReals, mutable=True | ||
| # Rate constants, make unknown parameters variables |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this example get refactored to use the Experiment class? Is this something @smondal13 is thinking about for a separate PR?
| #### Redesign with Experiment class Dec 2023 | ||
|
|
||
| # Options for using mpi-sppy or local EF only used in the deprecatedEstimator class | ||
| # TODO: move use_mpisppy to a Pyomo configuration option |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to get cleaned up? Is this for the deprecated interface only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will be cleaned up. Yes, currently the use_mpisppy option is only included in deprecated interface. This was added before I started this PR.
| for exp_i in exp_blocks: | ||
| vals = {} | ||
| for var in return_values: | ||
| exp_i_var = exp_i.find_component(str(var)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this doing? More comments would help? Is it assuming specific things about the structure of the parmest model? At a minimum, I would recommend documenting any assumptions in comments at least.
| var_values = pd.DataFrame(var_values) | ||
|
|
||
| # Calculate covariance if requested using cov_est() | ||
| if calc_cov is not NOTSET and calc_cov: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When would calc_cov be assigned NOTSET?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Addresses next few comments]. Need to confirm with @blnicho and team which tests need to be kept and enforced with these changes. If we call cov_est or _cov_at_theta here, then we are restraining the user to only using variables for unknown parameters, which makes multiple of the existing tests and examples fail. The inverse reduced hessian method was what @slilonfe5 implemented to calculate the covariance if a user still wishes to use theta_est for covariance. Previous conversations indicated these features should be preserved from my understanding.
Will also add more comments
| return obj_val, theta_vals, cov | ||
| elif calc_cov is NOTSET or not calc_cov: | ||
| return obj_val, theta_vals | ||
| measurement_var = self.obj_value / ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this work with the changes @slilonfe5 introduced to define different objectives?
| return obj_val, theta_vals, var_values, cov | ||
| elif calc_cov is NOTSET or not calc_cov: | ||
| return obj_val, theta_vals, var_values | ||
| solve_result, inv_red_hes = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this call the _cov_at_theta function?
| 1 | ||
| ) # initialization performed using just 1 set of theta values | ||
|
|
||
| # print("DEBUG objective_at_theta_blocks") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove these commented out print statements before we merge. They are okay to keep while debugging / working on other feedback.
Hello @adowling2, I agree. Stephen and I have been talking a lot about integrating the covariance estimation, as well as help with understanding the parmest test file. I will review and leave comments and suggestions. |
|
Notes to discuss in 01/20/26 meeting: Does the use of parameters and indexed parameters still need to be preserved? |
Hello @sscini. I think you want your code to be robust to those old parmest examples, we can discuss later. I will leave suggestions on the |
|
|
||
|
|
||
| # Need to make this more robust. Used in Estimator class | ||
| # Has issue where it counts duplicate data if multiple non-unique outputs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| # Has issue where it counts duplicate data if multiple non-unique outputs | |
| total_number_data = 0 | |
| for experiment in experiment_list: | |
| # get the experiment outputs | |
| output_variables = experiment.get_labeled_model().experiment_outputs | |
| # get the parent component of the first output variable | |
| parent = list(output_variables.keys())[0].parent_component() | |
| # check if there is only one unique experiment output, e.g., dynamic output variable | |
| if all(v.parent_component() is parent for v in output_variables): | |
| total_number_data += len(output_variables) | |
| else: | |
| total_number_data += 1 | |
| return total_number_data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sscini suggested code to fix the issue. I checked, and this works better.
| calc_cov=NOTSET, | ||
| cov_n=NOTSET, | ||
| ): | ||
| def _create_scenario_blocks(self, bootlist=None, ThetaVals=None, fix_theta=False): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sscini I think you need the calc_cov and cov_n arguments. It allows users who use theta_est for covariance calculation to continue doing so. If the user doesn't pass the calc_cov and cov_n arguments in theta_est, then calc_cov=NOTSET and cov_n=NOTSET.
Fixes # .
Summary/Motivation:
_Q_opt, the main model building and solving function within parmest, is still heavily dependent on MSISPPY, and the code is becoming outdated and difficult to interpret. The goal of this PR is to redesign _Q_opt to work without this dependence, retaining all the current functionality
Changes proposed in this PR:
-_Q_opt redesign, using a block structure, with minimal changes needed to functions that utilize it.
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: