Skip to content

Commit

Permalink
Add support for isothermal conditions in control volumes. (#1558)
Browse files Browse the repository at this point in the history
* Extended control volumes

* Extended CV with isothermal option

* Docs for extended CVs

* Black and pylint

* Apply suggestions from code review

Co-authored-by: Adam Atia <[email protected]>

* Fix XCV1D isothermal contraint

* Cleaning up some cruft in tests

* Typo in error messages

* Apply discretization in 1d tests

* Running black

* Rename to isothermal constraint and add type hints

* Running black

* Fixing duplicated import

* Updaing docs and doc strings

---------

Co-authored-by: Adam Atia <[email protected]>
  • Loading branch information
andrewlee94 and adam-a-a authored Jan 16, 2025
1 parent 2afe182 commit 6961bb5
Show file tree
Hide file tree
Showing 13 changed files with 772 additions and 1 deletion.
28 changes: 28 additions & 0 deletions docs/reference_guides/core/control_volume_0d.rst
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,31 @@ A single pressure balance is written for the entire mixture.
.. math:: 0 = s_{pressure} \times P_{in, t} - s_{pressure} \times P_{out, t} + s_{pressure} \times \Delta P_t + s_{pressure} \times \Delta P_{custom, t}

The :math:`\Delta P_{custom, t}` term allows the user to provide custom terms which will be added into the pressure balance.


Extended 0D Control Volume Class
--------------------------------

The ExtendedControlVolume0DBlock block builds upon ControlVolume0DBlock by adding some new balance options. It is envisioned that this will
merge with ControlVolume0DBlock, however to ensure backward compatibility these additions have been kept separate until unit models can
be updated to restrict (or allow) these new options if necessary. The core functionality is the same as for ControlVolume0DBlock, with the
addition of one extra energy balance type; isothermal.

.. module:: idaes.core.base.extended_control_volume0d

.. autoclass:: ExtendedControlVolume0DBlock
:members:

.. autoclass:: ExtendedControlVolume0DBlockData
:members:

add_isothermal_constraint
^^^^^^^^^^^^^^^^^^^^^^^^^

A constraint equating temperature at the inlet and outlet of the control volume is written.

**Constraints**

`isothermal_constraint(t)`:

.. math:: T_{in, t} == T_{out, t}
30 changes: 30 additions & 0 deletions docs/reference_guides/core/control_volume_1d.rst
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,33 @@ The :math:`\Delta P_{custom, t, x}` term allows the user to provide custom terms
`pressure_linking_constraint(t, x)`:

This constraint is an internal constraint used to link the pressure terms in the StateBlocks into a single indexed variable. This is required as Pyomo.DAE requires a single indexed variable to create the associated DerivativeVars and their numerical expansions.


Extended 1D Control Volume Class
--------------------------------

The ExtendedControlVolume1DBlock block builds upon ControlVolume1DBlock by adding some new balance options. It is envisioned that this will
merge with ControlVolume1DBlock, however to ensure backward compatibility these additions have been kept separate until unit models can
be updated to restrict (or allow) these new options if necessary. The core functionality is the same as for ControlVolume1DBlock, with the
addition of one extra energy balance type; isothermal.

.. module:: idaes.core.base.extended_control_volume1d

.. autoclass:: ExtendedControlVolume1DBlock
:members:

.. autoclass:: ExtendedControlVolume1DBlockData
:members:

add_isothermal_constraint
^^^^^^^^^^^^^^^^^^^^^^^^^

A constraint equating temperature along the length domain of the control volume is written.

**Constraints**

`isothermal_constraint(t, x)`:

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

This constraint is skipped at the inlet to the control volume.
2 changes: 2 additions & 0 deletions idaes/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
)
from .base.control_volume0d import ControlVolume0DBlock
from .base.control_volume1d import ControlVolume1DBlock, DistributedVars
from .base.extended_control_volume0d import ExtendedControlVolume0DBlock
from .base.extended_control_volume1d import ExtendedControlVolume1DBlock
from .base.phases import (
Phase,
LiquidPhase,
Expand Down
10 changes: 10 additions & 0 deletions idaes/core/base/control_volume0d.py
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,16 @@ def add_total_energy_balances(self, *args, **kwargs):
"add_total_energy_balances.".format(self.name)
)

def add_isothermal_constraint(self, *args, **kwargs):
"""
Requires ExtendedControlVolume0D
"""
raise BalanceTypeNotSupportedError(
f"{self.name} ControlVolume0D does not support isothermal energy balances. "
"Please consider using ExtendedControlVolume0D in your model if you require "
"support for isothermal balances."
)

def add_total_pressure_balances(self, has_pressure_change=False, custom_term=None):
"""
This method constructs a set of 0D pressure balances indexed by time.
Expand Down
10 changes: 10 additions & 0 deletions idaes/core/base/control_volume1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -1737,6 +1737,16 @@ def add_total_energy_balances(self, *args, **kwargs):
"add_total_energy_balances.".format(self.name)
)

def add_isothermal_constraint(self, *args, **kwargs):
"""
Requires ExtendedControlVolume1D
"""
raise BalanceTypeNotSupportedError(
f"{self.name} ControlVolume1D does not support isothermal energy balances. "
"Please consider using ExtendedControlVolume1D in your model if you require "
"support for isothermal balances."
)

def add_total_pressure_balances(self, has_pressure_change=False, custom_term=None):
"""
This method constructs a set of 1D pressure balances indexed by time.
Expand Down
15 changes: 15 additions & 0 deletions idaes/core/base/control_volume_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class EnergyBalanceType(Enum):
enthalpyTotal = 2
energyPhase = 3
energyTotal = 4
isothermal = 5


# Enumerate options for momentum balances
Expand Down Expand Up @@ -618,6 +619,8 @@ def add_energy_balances(self, balance_type=EnergyBalanceType.useDefault, **kwarg
eb = self.add_total_energy_balances(**kwargs)
elif balance_type == EnergyBalanceType.energyPhase:
eb = self.add_phase_energy_balances(**kwargs)
elif balance_type == EnergyBalanceType.isothermal:
eb = self.add_isothermal_constraint(**kwargs)
else:
raise ConfigurationError(
"{} invalid balance_type for add_energy_balances."
Expand Down Expand Up @@ -843,6 +846,18 @@ def add_total_energy_balances(self, *args, **kwargs):
"developer of the ControlVolume class you are using.".format(self.name)
)

def add_isothermal_constraint(self, *args, **kwargs):
"""
Method for adding an isothermal constraint to the control volume.
See specific control volume documentation for details.
"""
raise NotImplementedError(
f"{self.name} control volume class has not implemented a method for "
"add_isothermal_constraint. Please contact the "
"developer of the ControlVolume class you are using."
)

def add_phase_pressure_balances(self, *args, **kwargs):
"""
Method for adding pressure balances indexed by
Expand Down
112 changes: 112 additions & 0 deletions idaes/core/base/extended_control_volume0d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#################################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2024 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
"""
0D Control Volume class with support for isothermal energy balance.
"""

__author__ = "Andrew Lee"

from pyomo.environ import Constraint, Expression

# Import IDAES cores
from idaes.core.base.control_volume0d import ControlVolume0DBlockData
from idaes.core import declare_process_block_class
from idaes.core.util.exceptions import ConfigurationError

import idaes.logger as idaeslog

_log = idaeslog.getLogger(__name__)


@declare_process_block_class(
"ExtendedControlVolume0DBlock",
doc="""
ExtendedControlVolume0DBlock is an extension of the ControlVolume0D
block with support for isothermal conditions in place of a formal
energy balance.""",
)
class ExtendedControlVolume0DBlockData(ControlVolume0DBlockData):
"""
Extended 0-Dimensional (Non-Discretized) ControlVolume Class
This class extends the existing ControlVolume0DBlockData class
with support for isothermal energy balances.
"""

def add_isothermal_constraint(
self,
has_heat_of_reaction: bool = False,
has_heat_transfer: bool = False,
has_work_transfer: bool = False,
has_enthalpy_transfer: bool = False,
custom_term: Expression = None,
) -> Constraint:
"""
This method constructs an isothermal constraint for the control volume.
Arguments are supported for compatibility with other forms but must be False
or None otherwise an Exception is raised.
Args:
has_heat_of_reaction: whether terms for heat of reaction should
be included in enthalpy balance
has_heat_transfer: whether terms for heat transfer should be
included in enthalpy balances
has_work_transfer: whether terms for work transfer should be
included in enthalpy balances
has_enthalpy_transfer: whether terms for enthalpy transfer due to
mass transfer should be included in enthalpy balance. This
should generally be the same as the has_mass_transfer
argument in the material balance methods
custom_term: a Python method which returns Pyomo expressions representing
custom terms to be included in enthalpy balances.
Method should accept time and phase list as arguments.
Returns:
Constraint object representing isothermal constraint
"""
if has_heat_transfer:
raise ConfigurationError(
f"{self.name}: 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 and add a constraint to equate inlet and outlet temperatures."
)
if has_work_transfer:
raise ConfigurationError(
f"{self.name}: 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 and add a constraint to equate inlet and outlet temperatures."
)
if has_enthalpy_transfer:
raise ConfigurationError(
f"{self.name}: isothermal energy balance option does not support enthalpy transfer."
)
if has_heat_of_reaction:
raise ConfigurationError(
f"{self.name}: isothermal energy balance option requires that has_heat_of_reaction is False. "
"If you are trying to solve for heat duty to achieve isothermal operation, please use "
"a full energy balance and add a constraint to equate inlet and outlet temperatures."
)
if custom_term is not None:
raise ConfigurationError(
f"{self.name}: isothermal energy balance option does not support custom terms."
)

# Add isothermal constraint
@self.Constraint(
self.flowsheet().time, doc="Isothermal constraint - replaces energy balance"
)
def isothermal_constraint(b, t):
return b.properties_in[t].temperature == b.properties_out[t].temperature

return self.isothermal_constraint
121 changes: 121 additions & 0 deletions idaes/core/base/extended_control_volume1d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#################################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2024 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
"""
1D Control Volume class with support for isothermal energy balance.
"""

__author__ = "Andrew Lee"

# Import Pyomo libraries
from pyomo.environ import Constraint, Expression

# Import IDAES cores
from idaes.core.base.control_volume1d import ControlVolume1DBlockData
from idaes.core import declare_process_block_class
from idaes.core.util.exceptions import ConfigurationError

import idaes.logger as idaeslog

_log = idaeslog.getLogger(__name__)


@declare_process_block_class(
"ExtendedControlVolume1DBlock",
doc="""
ExtendedControlVolume1DBlock is an extension of the ControlVolume1D
block with support for isothermal conditions in place of a formal
energy balance.""",
)
class ExtendedControlVolume1DBlockData(ControlVolume1DBlockData):
"""
Extended 1-Dimensional ControlVolume Class
This class extends the existing ControlVolume1DBlockData class
with support for isothermal energy balances.
"""

def add_isothermal_constraint(
self,
has_heat_of_reaction: bool = False,
has_heat_transfer: bool = False,
has_work_transfer: bool = False,
has_enthalpy_transfer: bool = False,
custom_term: Expression = None,
) -> None:
"""
This method constructs an isothermal constraint for the control volume.
Arguments are supported for compatibility with other forms but must be False
or None otherwise an Exception is raised.
Args:
has_heat_of_reaction: whether terms for heat of reaction should
be included in enthalpy balance
has_heat_transfer: whether terms for heat transfer should be
included in enthalpy balances
has_work_transfer: whether terms for work transfer should be
included in enthalpy balances
has_enthalpy_transfer: whether terms for enthalpy transfer due to
mass transfer should be included in enthalpy balance. This
should generally be the same as the has_mass_transfer
argument in the material balance methods
custom_term: a Python method which returns Pyomo expressions representing
custom terms to be included in enthalpy balances.
Method should accept time and phase list as arguments.
Returns:
Constraint object representing isothermal constraints
"""
if has_heat_transfer:
raise ConfigurationError(
f"{self.name}: 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 and add a constraint to equate inlet and outlet temperatures."
)
if has_work_transfer:
raise ConfigurationError(
f"{self.name}: 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 and add a constraint to equate inlet and outlet temperatures."
)
if has_enthalpy_transfer:
raise ConfigurationError(
f"{self.name}: isothermal energy balance option does not support enthalpy transfer. "
)
if has_heat_of_reaction:
raise ConfigurationError(
f"{self.name}: isothermal energy balance option requires that has_heat_of_reaction is False. "
"If you are trying to solve for heat duty to achieve isothermal operation, please use "
"a full energy balance and add a constraint to equate inlet and outlet temperatures."
)
if custom_term is not None:
raise ConfigurationError(
f"{self.name}: isothermal energy balance option does not support custom terms. "
)

# Add isothermal constraint
@self.Constraint(
self.flowsheet().time,
self.length_domain,
doc="Isothermal constraint - replaces energy balances",
)
def isothermal_constraint(b, t, x):
if x == b.length_domain.first():
return Constraint.Skip

return (
b.properties[t, b.length_domain.prev(x)].temperature
== b.properties[t, x].temperature
)

return self.isothermal_constraint
Loading

0 comments on commit 6961bb5

Please sign in to comment.