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

Add support for isothermal conditions in control volumes. #1558

Merged
merged 15 commits into from
Jan 16, 2025

Conversation

andrewlee94
Copy link
Member

Fixes None

Summary/Motivation:

WateTAP has a number of cases where they would like to use isothermal conditions in place of an energy balance. Currently, they use a mix-in to support this, but this adds an addition argument that is effectively redundant. Instead, it would be better to have explicit support for isothermal conditions as part of the core ControlVolume classes.

However, isothermal conditions do not make sense for all unit models, and there are a few cases where unit models have effectively implemented their own isothermal conditions (notable PressureChanger). Thus, to avoid potential edge cases in unit models until they can be updated, a new ExtendedControlVolume0D and ExtenededControlVolume1D class have been created as an interim solution. It is envisioned that unit models will gradually be updated to be more explicit about what balance types make sense, at which point the new and old control volumes can be merged.

Changes proposed in this PR:

  • Added new isothermal option to EnergyBalanceType Enum.
  • Added BalanceTypeNotSupported exceptions to ControlVolume0D and ControlVolume1D if isothermal option is selected, with pointer to the new control volume classes.
  • New ExtendedControlVolume0D and ExtendedControlVolume1D classes with support for isothermal balances.
  • Docs and tests.

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.

@andrewlee94 andrewlee94 self-assigned this Jan 15, 2025
@andrewlee94 andrewlee94 added enhancement New feature or request Priority:Normal Normal Priority Issue or PR core Issues dealing with core modeling components backward-compat Affects backward compatibility WaterTAP labels Jan 15, 2025
@adam-a-a adam-a-a requested a review from bknueven January 15, 2025 16:55
Comment on lines +120 to +122
return (
b.properties[t, b.length_domain.prev(x)].temperature
== b.properties[t, x].temperature
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the constraint would be skipped at the outlet when using forward scheme, but what does this mean for temperature at the outlet?

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, most of this is not needed as there is no partial derivative. The way this is written, it has to skip the first point no matter what.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

I might be misunderstanding, but as written, it appears that for backwards transformation, temperature at the inlet (x=0) would be set equal to the temperature of the next element, and this equality would be propagated across adjacent pairs for the whole length domain. So the temperature for each element would be covered.

However, for the forward transformation case, it seems that all elements except the last element would be covered by the equality constraint.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, now I think the issue is fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we also rename enthalpy_balances to isothermal_constraint or something here?

m.fs = Flowsheet(dynamic=False)
m.fs.pp = PhysicalParameterTestBlock()
m.fs.rp = ReactionParameterTestBlock(property_package=m.fs.pp)
m.fs.pp.del_component(m.fs.pp.phase_equilibrium_idx)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this necessary?

Suggested change
m.fs.pp.del_component(m.fs.pp.phase_equilibrium_idx)
m.fs.pp.del_component(m.fs.pp.phase_equilibrium_idx)

Copy link
Member Author

Choose a reason for hiding this comment

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

It isn't - just left over from copying an old test (where it was likely not needed either).

Copy link
Member Author

Choose a reason for hiding this comment

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

Cleaned up

== m.fs.cv.properties_out[t].temperature
)

assert_units_consistent(m.fs.cv)
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting- I thought I remember the units check failing for dynamic cases, or is that only when has_holdup=True as well?

Copy link
Member Author

@andrewlee94 andrewlee94 Jan 15, 2025

Choose a reason for hiding this comment

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

It will fail for anything with a partial derivative. Here we get aways with it because we don't have a derivative.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not necessarily. Generally for spatial domains we normalize the ContinuousSet to run from 0 to 1, adding in the flow length later. Those should pass. Any ContinuousSet with dimensions, however, will cause it to fail.

ConfigurationError,
match="fs.cv: isothermal energy balance option requires that has_heat_transfer is False. "
"If you are trying to solve for heat duty to achieve isothermal operation, please use "
"a full energy balance as add a constraint to equate inlet and outlet temperatures.",
Copy link
Contributor

Choose a reason for hiding this comment

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

This error message needs some revision: "a full energy balance as add a constraint to equate inlet and outlet temperatures."

Copy link
Member Author

Choose a reason for hiding this comment

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

Fix: "and add"

ConfigurationError,
match="fs.cv: isothermal energy balance option requires that has_work_transfer is False. "
"If you are trying to solve for work under isothermal operation, please use "
"a full energy balance as add a constraint to equate inlet and outlet temperatures.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Same issue- just need to revise this segment of the error message and propagate through.

Copy link
Member Author

Choose a reason for hiding this comment

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

As above.

m.fs.cv.add_isothermal_constraint()

assert isinstance(m.fs.cv.enthalpy_balances, Constraint)
assert len(m.fs.cv.enthalpy_balances) == 1 # x==0 is skipped
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought the length of enthalpy_balances would be fine_elements - 1. Maybe I am missing something (or the test is failing).

Copy link
Member Author

Choose a reason for hiding this comment

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

The model has not been discretised, so there are only 2 points.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh! I see now. Didn't catch that--didn't apply transformation.

m.fs.cv.add_isothermal_constraint()

assert isinstance(m.fs.cv.enthalpy_balances, Constraint)
assert len(m.fs.cv.enthalpy_balances) == 4 # x==0 is skipped
Copy link
Contributor

Choose a reason for hiding this comment

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

Similarly, would expect this to be of length equal to (finite_elements - 1) * len(time_set)

Copy link
Member Author

Choose a reason for hiding this comment

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

It is, it is just (2-1)*4 = 4.

Copy link
Contributor

Choose a reason for hiding this comment

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

finite elements=10 not 2 here

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, just saw your previous response. Any reason not to apply transformation and check?

Copy link
Member Author

Choose a reason for hiding this comment

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

Reduce test overhead.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could be good just to do a transformation with, say, 3 finite elements.

Copy link
Member Author

Choose a reason for hiding this comment

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

I debated it, but decided that was as much a test of Pyomo functionality as of IDAES (and thus I was going to trust Pyomo).

Copy link
Member Author

Choose a reason for hiding this comment

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

I realised there is a reason to test this, but not for the obvious reason - when using the domain.prev() method it works on the domain as it appears at the time of the call, meaning that it will always create T[0, 0] == T[0, 1] and then fill in intermediate points. In this case, it should not matter, but it needs to be tested.

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 - I don't think it would hurt too much to add a test with transformation applied to 1d CV (with 2 - 3 finite elements), but I'll leave that to other reviewers and @andrewlee94

Copy link
Contributor

@dallan-keylogic dallan-keylogic left a comment

Choose a reason for hiding this comment

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

Some minor changes, otherwise it looks good.

Comment on lines 46 to 50
has_heat_of_reaction=False,
has_heat_transfer=False,
has_work_transfer=False,
has_enthalpy_transfer=False,
custom_term=None,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add Type Hints here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Might as well - if we don't do it now it probably won't get done.

Comment on lines 104 to 106
@self.Constraint(self.flowsheet().time, doc="Energy balances")
def enthalpy_balances(b, t):
return b.properties_in[t].temperature == b.properties_out[t].temperature
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we rename this constraint to something that better describe what it does? Otherwise you might, for example, get enthalpy scaling on a constraint that should have temperature scaling.

Copy link
Member Author

Choose a reason for hiding this comment

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

I debated whether the change the name - my reason for keeping it the same was for consistency of naming between different configurations (so that the "energy balance" would always have the same name no matter what option you chose).

However, I did not think about scaling, and having a descriptive name would be far more useful there for people doing custom scaling. I'll change it.

Comment on lines +120 to +122
return (
b.properties[t, b.length_domain.prev(x)].temperature
== b.properties[t, x].temperature
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we also rename enthalpy_balances to isothermal_constraint or something here?

Copy link
Contributor

@dallan-keylogic dallan-keylogic left a comment

Choose a reason for hiding this comment

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

A few more documentation quibbles.

Comment on lines 310 to 312
`enthalpy_balances(t)`:

.. math:: T_{in, t} == T_{out, t}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is now outdated

Comment on lines 339 to 341
`enthalpy_balances(t)`:

.. math:: T_{t, x-1} == T_{t, x}
Copy link
Contributor

Choose a reason for hiding this comment

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

Outdated.

Method should accept time and phase list as arguments.

Returns:
Constraint object representing enthalpy balances
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be good to update this to reflect isothermal constraint.

Method should accept time and phase list as arguments.

Returns:
Constraint object representing enthalpy balances
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be good to update this to reflect isothermal constraint.

Copy link
Contributor

@dallan-keylogic dallan-keylogic left a comment

Choose a reason for hiding this comment

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

LGTM

@andrewlee94 andrewlee94 enabled auto-merge (squash) January 16, 2025 17:51
@andrewlee94 andrewlee94 disabled auto-merge January 16, 2025 18:44
@andrewlee94 andrewlee94 merged commit 6961bb5 into IDAES:main Jan 16, 2025
39 of 41 checks passed
@andrewlee94 andrewlee94 deleted the isothermal_cv branch January 16, 2025 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backward-compat Affects backward compatibility core Issues dealing with core modeling components enhancement New feature or request Priority:Normal Normal Priority Issue or PR WaterTAP
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants