-
Notifications
You must be signed in to change notification settings - Fork 238
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
Add wall temperature and heat accumulation terms to the 0D heat exchanger #714
Conversation
…dated failing regression test
…id_dynamic() and save to file
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 an initial read through, this looks really good. My only comments were a couple of minor things.
|
||
def initialize(self, *args, **kwargs): | ||
|
||
if self.flowsheet().config.dynamic: |
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.
My initial though is that this should use the local self.config.dynamic
flag - is there a reason this will not work?
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.
The reason for this is that I want to use control volumes with dynamic=False
. And the way the base class is written, the control volumes inherit their dynamic
flags from the unit model config. In heat_exchanger.py
we have:
hot_side = _make_heater_control_volume(
self,
config.hot_side_name,
config.hot_side_config,
dynamic=config.dynamic,
has_holdup=config.has_holdup,
)
What you're suggesting would call for a config like so:
m.fs.unit = HeatExchangerLumpedCapacitance(default={
"shell": {"property_package": m.fs.properties,
"dynamic": False},
"tube": {"property_package": m.fs.properties,
"dynamic": False},
"dynamic": True})
And then, for instance, in heat_exchanger.py
:
try:
hot_dynamic = hot_side_config.dynamic
except KeyError:
hot_dynamic = config.dynamic
hot_side = _make_heater_control_volume(
self,
config.hot_side_name,
config.hot_side_config,
dynamic=hot_dynamic,
has_holdup=config.has_holdup,
)
That would give us three different levels where the dynamic
flag could be configured: flowsheet -> unit model -> control volume. And in that case, we should probably do the same for has_holdup
. Actually, the new logic probably belongs in _process_config()
.
I'd be happy to try this, but I thought it best not to make any changes to heat_exchanger.py
in case someone else is also working on it. Let me know which you prefer, to leave it as-is, or to make the changes to both unit models and have dynamic
be configurable at each level.
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.
I am not sure I follow here. Are you saying that you always want the control volumes to be steady-state (i.e. dynamic=False
) and even thought the unit model (and flowsheet) might be dynamic? If so, this is not the way to do it, as currently the dynamic flag is inherited by default and you want to over-ride that (which is currently not happening).
If that is what you want to do, then I can see two ways of going about this.
- The more correct way would be to not inherit from the heat exchanger model at all and build an entirely new model (as the difference, whilst relatively small, are unfortunately far reaching). In doing so, you could decouple the control volume dynamic flag form the unit model dynamic flag.
- The second way, which would be simpler but still have the disconnect between unit and flowsheet, would look much like what you already have, but you would need to change the unit level configuration argument for the
dynamic
flag (and thehas_holdup
flag). What you would need to do is change their settings so that the default value isFalse
and that the valid domain is alsoFalse
- that forces the unit model itself to always be steady-state (and prevents it from inheriting from the flowsheet).
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.
Are you saying that you always want the control volumes to be steady-state (i.e. dynamic=False) and even thought the unit model (and flowsheet) might be dynamic?
I don't want them to always be steady-state. But I'd like to leave that in as an option. With the example that's included, I'm essentially assuming that while there's heat holdup in the wall material, any mass holdup in the working fluid is negligible. So the way it's implemented now, the dynamic
and has_holdup
flags only affect the control volumes.
The more correct way would be to not inherit from the heat exchanger model at all and build an entirely new model (as the difference, whilst relatively small, are unfortunately far reaching).
I was afraid it might come to that. That sounds like the most thorough approach. The downside though, would be lots of duplicate code.
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.
First, I think we need to work out exactly what you want to allow for, as I am not quite sure what is happening. Is it that you want to allow separate the decision about holdup dynamics from that of the wall energy balance then - i.e. allow the wall energy balance to be dynamic even if the holdups are steady-state?
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.
Is it that you want to allow separate the decision about holdup dynamics from that of the wall energy balance then - i.e. allow the wall energy balance to be dynamic even if the holdups are steady-state?
Yes exactly!
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.
OK, my first thought then is that we should allow the user to choose wall dynamics separately from the material balance - I.e. have a separate configuration option for the wall dynamics that the user can set, and then have some logic on the default setting (much like how the material dynamics are done).
We might want to think about any combinations that do not make sense however - I am not sure that there are any however (at most it would be setting everything to steady-state, in which case this devolves into the basic heat exchanger, and that will still work).
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.
OK, my first thought then is that we should allow the user to choose wall dynamics separately from the material balance - I.e. have a separate configuration option for the wall dynamics that the user can set, and then have some logic on the default setting (much like how the material dynamics are done).
This is a nice solution. If there are no other concerns or objections, I'll go ahead and implement it. Do you have a preference for the new variable name? I was thinking either dynamic_wall
or dynamic_heat_balance
. Also, are there any other similar unit models? Because it might be good to standardize or reuse this variable name if possible.
We might want to think about any combinations that do not make sense however - I am not sure that there are any however (at most it would be setting everything to steady-state, in which case this devolves into the basic heat exchanger, and that will still work).
Agreed. That did occur to me. My initial thought was that there wasn't a use case for having everything be steady-state, since one could just use the base heat exchanger model. But with this model, you at least get the wall temperature in addition. So that might still be useful to have. Especially if you're troubleshooting a flowsheet or model that's difficult to solve, it could be helpful to see this temperature at steady-state.
Also, if you're in "troubleshooting mode" I think it's better to change something with a flag rather than swapping out the entire unit model. Since using a different unit model introduces more unknowns. So I think it's important to make sure this model still works with steady-state flowsheets.
The same argument could be made for the other config combinations. The user might want to make sure their model solves with dynamic control volumes before activating the wall dynamics. And vice versa too.
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.
dynamic_heat_balance
is the best of anything I can think of so far.
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.
dynamic_heat_balance
is the best of anything I can think of so far.
Ok done! I also added a test to make sure each combination of dynamic / dynamic_heat_balance can be built. And some xfail-ing tests for invalid configurations.
idaes/generic_models/unit_models/tests/test_heat_exchanger_lc.py
Outdated
Show resolved
Hide resolved
docs/reference_guides/model_libraries/generic/unit_models/heat_exchanger_lc.rst
Show resolved
Hide resolved
Codecov Report
@@ Coverage Diff @@
## main #714 +/- ##
==========================================
+ Coverage 64.58% 64.77% +0.18%
==========================================
Files 371 375 +4
Lines 59294 60130 +836
Branches 10923 11088 +165
==========================================
+ Hits 38293 38947 +654
- Misses 19049 19195 +146
- Partials 1952 1988 +36
Continue to review full report at Codecov.
|
…t to docs page, removed unneeded CONFIG line in heat_exchanger_lc
…d capacitance config
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.
Looking good. I just had a couple of suggestions on how to improve the usability of the new config argument and on a way to replace some of the xfails
with tests that will check the error messages.
"shell": {"property_package": m.fs.properties}, | ||
"tube": {"property_package": m.fs.properties}, | ||
"dynamic": False, | ||
"dynamic_heat_balance": True}) |
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.
For these cases, we generally prefer the following to an xfail
:
with pytest.raises(DAE_Error, match="error message"):
m.fs.unit = HeatExchangerLumpedCapacitance(default={
"shell": {"property_package": m.fs.properties},
"tube": {"property_package": m.fs.properties},
"dynamic": False,
"dynamic_heat_balance": True})
The reason being that this is actually a successfully test of an expected failure, so this way the test shows up as passed, plus it also checks that the error message is what was expected.
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.
That makes sense. I didn't do this for the InconsistentUnitsError though, since that's not something we really intended. And this doesn't seem to be caused by IDAES. So I'll try and setup a simple example and submit an issue to the Pyomo folks.
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.
@rustygentile Pyomo is already aware of the issue with units: Pyomo/pyomo#1790. However, more people commenting about this being a problem might help motivate a fix.
CONFIG.declare( | ||
"dynamic_heat_balance", | ||
ConfigValue( | ||
default=True, |
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.
Two comments here:
- Do we want to allow this to take a default value based on the flowsheet, in the same way the
dynamic
flag does? In this case, we should make the defaultuseDefault
and the domainDefaultBool
(see https://github.com/IDAES/idaes-pse/blob/main/idaes/core/unit_model.py). - I think it would be helpful to add validation of this config argument in the code (and it would be necessary if we do suggestion 1). E.g. we should check for
dynamic_heat_balance=True
in flowsheets wheredynamic=False
and raise an exception of our own to inform users what happened. Otherwise, they will eventually see aDAE_Error
, but that is likely not going to help them understand what they did wrong and how to fix it.
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.
Agreed. useDefault
makes sense here. Regarding point 2., I found this: https://github.com/IDAES/idaes-pse/blob/main/idaes/core/util/exceptions.py, which seems more appropriate than DAE_Error
…acitance, added useDefault to dynamic_heat_balance
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.
Looks good to me.
Fixes
See: #673
Summary/Motivation:
Adding a new unit model that extends the 0D HeatExchanger by including wall temperature and heat holdup terms in the overall energy balance. This will improve transient simulations.
Changes proposed in this PR:
HeatExchangerLumpedCapacitance
unit model classLegal Acknowledgement
By contributing to this software project, I agree to the following terms and conditions for my contribution: