Skip to content

Conversation

@radhakrishnatg
Copy link
Contributor

Summary/Motivation:

This PR adds new features to the price-taker class. This is WIP. This PR needs to be merged after merging #1579

Changes proposed in this PR:

  • Fix a bug: Capacity limit constraints, startup-shutdown constraints, etc. would not work for indexed operation blocks. This is fixed in this PR.
  • Added methods for retrieving all operational and design variable values as pd.DataFrame and dict, respectively.
  • Added a method for plotting operational profiles.
  • Added a method for plotting LMP histograms
  • Added a generalized storage model class. If an instance of StorageModel is found in the flowsheet instance, then linking constraints are now added automatically.
  • Added support for automatic construction of polynomial-type surrogates.

Legal Acknowledgement

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

  1. I agree my contributions are submitted under the license terms described in the LICENSE.txt file at the top level of this directory.
  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.

@radhakrishnatg radhakrishnatg marked this pull request as draft June 10, 2025 20:57
@radhakrishnatg radhakrishnatg self-assigned this Jun 10, 2025
if self.config.fixed_design_data is not None:
# The design is fixed, so all the desired quantities are defined as parameters
for param_name, param_value in self.config.fixed_design_data.items():
setattr(self, param_name, Param(initialize=param_value, mutable=True))
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason why you chose mutable Params over fixing Vars ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not quite sure which option is preferable. If we define a Var and fix it, it would allow the user to unfix it. If they do that, then the design variable would be unconstrained and it may lead to an unexpected behavior.
I feel that declaring it as a Param would prevent unfixing and it ensures that the design is always fixed. I can change it to fixing Var if that is the convention we are following everywhere.

@codecov
Copy link

codecov bot commented Jul 23, 2025

Codecov Report

❌ Patch coverage is 51.02881% with 119 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.36%. Comparing base (d4d10ea) to head (59e6291).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...s/grid_integration/pricetaker/price_taker_model.py 12.50% 119 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1633      +/-   ##
==========================================
- Coverage   77.46%   77.36%   -0.10%     
==========================================
  Files         394      394              
  Lines       64746    64979     +233     
  Branches    10896    10947      +51     
==========================================
+ Hits        50156    50273     +117     
- Misses      12081    12197     +116     
  Partials     2509     2509              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +400 to +401
@declare_process_block_class("StorageModel")
class StorageModelData(ProcessBlockData):
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any reason why we don't just have this in a separate file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is a short piece of code, so I just retained it in the same file. Anyways, users will be importing models from idaes.apps.grid_integration, so I thought it doesn't matter if we put it in this file or in a separate file.

Comment on lines 474 to 491
def _add_upper_bound(var_name, bound):
if isinstance(bound, (Var, Expression)):
setattr(
self,
var_name + "ub_con",
Constraint(
expr=getattr(self, var_name) <= bound,
doc=f"Constrains the maximum value of {var_name}",
),
)

else:
getattr(self, var_name).setub(bound)

_add_upper_bound("charge_rate", self.config.max_charge_rate)
_add_upper_bound("discharge_rate", self.config.max_discharge_rate)
_add_upper_bound("final_holdup", self.config.max_holdup)
_add_upper_bound("initial_holdup", self.config.max_holdup)
Copy link
Contributor

Choose a reason for hiding this comment

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

If I understand correctly, this _add_upper_bound function essentially provides the upper bound for a variable by adding a constraint (named m.charge_rate_ub_con in the case of charge_rate, for example) when a Var or Expression is provided.

However, when a float, int, etc., is used, this constraint would not be constructed and the upper bound of the Var would just be set directly.

While it seems like a trivial and subtle difference, it also seems less than ideal to allow variation in model structure like this (where one version includes upper bound constraints and another analogous version does not, simply based on input provided).

Is there a reason why we don't just (1) check whether instance is a Var or Expression, and if so, just do setub(pyo.value(bound)) ? The only potential issue that I can foresee with that is if an indexed Var or Expression is passed in, but haven't checked. Are there other reasons?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The Var and Expression are needed for the case where the design is a decision variable. Since the value of the variable is unknown, setub(pyo.value(bound)) will not work.
I agree that having different behavior for int/float vs. Var/Expression is not ideal. But, we cannot avoid it. To keep it consistent, we can implement all them as constraints regardless of the data type. But, that may impact the performance of some solvers. So, for now, I thought we can leave it as it is.

@adam-a-a adam-a-a marked this pull request as ready for review July 31, 2025 17:56
@ksbeattie ksbeattie added Priority:Normal Normal Priority Issue or PR WaterTAP labels Aug 7, 2025
@ksbeattie
Copy link
Member

@radhakrishnatg, @adam-a-a: Moving this to the Nov release.

@ksbeattie
Copy link
Member

@radhakrishnatg, @adam-a-a: Are you able now to look into this?

@adam-a-a
Copy link
Contributor

adam-a-a commented Oct 9, 2025

@ksbeattie the main motivation behind this PR is related to incorporating a pricetaker model in WaterTAP.

However, when pointing to this PR from WaterTAP, the current version of IDAES breaks tests on TAP.

we could still bring this pr in anyhow, but a bigger issue would be getting watertap to work with latest idaes

@ksbeattie
Copy link
Member

@radhakrishnatg should now be able to revisit this.

@ksbeattie
Copy link
Member

@adam-a-a would like it if this could stay in the Nov 2025 release.

@ksbeattie ksbeattie requested review from MarcusHolly and Morgan88888888 and removed request for adowling2 and jsiirola November 20, 2025 21:31
Copy link
Contributor

@MarcusHolly MarcusHolly left a comment

Choose a reason for hiding this comment

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

Minor comments - otherwise LGTM

Comment on lines +209 to +210
def _build_variable_design_model(self):
data = self.config.variable_design_data
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing a docstring

Comment on lines +336 to +339
CONFIG.declare(
"capacity",
ConfigValue(doc="Maximum capacity of the commodity"),
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Specify domain for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The input can be anything: Var, Expression, int, or float. So, I did not specify the domain.

@adam-a-a
Copy link
Contributor

@ksbeattie @sufikaur we're unsure why codecov seemingly isn't registering tests intended to address uncovered lines and increase coverage

Copy link
Contributor

@adam-a-a adam-a-a left a comment

Choose a reason for hiding this comment

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

LGTM - any remaining issues can be logged in an issue.

@ksbeattie ksbeattie merged commit 7745b43 into IDAES:main Nov 26, 2025
44 of 45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority:Normal Normal Priority Issue or PR WaterTAP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants