Hide code cell content
###############################################################################
# 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-2023 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.
###############################################################################

Heat Exchanger 0D Unit Model with Ideal & IAPWS Property Package#

Author: Anuja Deshpande
Maintainer: Brandon Paul
Updated: 2023-06-01

Problem Statement: In this example, we will be heating a benzene-toluene mixture using steam.

Tube Side Inlet

Flow Rate = 250 mol/s

Mole fraction (Benzene) = 0.4

Mole fraction (Toluene) = 0.6

Pressure = 101325 Pa

Temperature = 350 K

Shell Side Inlet

Flow Rate = 100 mol/s

Mole fraction (Steam) = 1

Pressure = 101325 Pa

Temperature = 450 K

This example will demonstrate the simulation of the 0D heat exchanger by fixing any 2 of the following degrees of freedom:

  • heat transfer area

  • overall heat transfer coefficient

  • minimum approach temperature

IDAES documentation reference for heat exchanger 0D model: https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/unit_models/heat_exchanger.html

Setting up the problem in IDAES

# Import pyomo package
from pyomo.environ import ConcreteModel, SolverFactory, Constraint, value, units

# Import idaes logger to set output levels
import idaes.logger as idaeslog

# Import the main FlowsheetBlock from IDAES. The flowsheet block will contain the unit model
from idaes.core import FlowsheetBlock

# import the BTX property package to create a properties block for the flowsheet
from idaes.models.properties.activity_coeff_models import BTX_activity_coeff_VLE

# Import the IAPWS property package to create a properties block for the flowsheet
from idaes.models.properties import iapws95

from idaes.models.properties.iapws95 import htpx

from idaes.models.properties.modular_properties import GenericParameterBlock

from idaes.models.properties.modular_properties.examples.BT_ideal import configuration

# Import the degrees_of_freedom function from the idaes.core.util.model_statistics package
from idaes.core.util.model_statistics import degrees_of_freedom

# Import a heat exchanger unit
from idaes.models.unit_models.heat_exchanger import (
    HeatExchanger,
    delta_temperature_amtd_callback,
)

# Create the ConcreteModel and the FlowsheetBlock, and attach the flowsheet block to it.
m = ConcreteModel()

# Steady State Model
m.fs = FlowsheetBlock(dynamic=False)

# Setup property packages for shell and tube side
# Steam property package
m.fs.properties_shell = iapws95.Iapws95ParameterBlock()

# BT ideal property package
m.fs.properties_tube = GenericParameterBlock(**configuration)
2024-05-28 17:23:11 [ERROR] idaes.core.base.process_block: Failure in build: fs.properties_shell
Traceback (most recent call last):
  File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py", line 41, in _rule_default
    b.build()
  File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/models/properties/general_helmholtz/helmholtz_functions.py", line 1441, in build
    raise RuntimeError("Helmholtz EoS external functions not available")
RuntimeError: Helmholtz EoS external functions not available
ERROR: Constructing component 'fs.properties_shell' from data=None failed:
        RuntimeError: Helmholtz EoS external functions not available
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[2], line 39
     35 m.fs = FlowsheetBlock(dynamic=False)
     37 # Setup property packages for shell and tube side
     38 # Steam property package
---> 39 m.fs.properties_shell = iapws95.Iapws95ParameterBlock()
     41 # BT ideal property package
     42 m.fs.properties_tube = GenericParameterBlock(**configuration)

File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:571, in BlockData.__setattr__(self, name, val)
    566 if name not in self.__dict__:
    567     if isinstance(val, Component):
    568         #
    569         # Pyomo components are added with the add_component method.
    570         #
--> 571         self.add_component(name, val)
    572     else:
    573         #
    574         # Other Python objects are added with the standard __setattr__
    575         # method.
    576         #
    577         super(BlockData, self).__setattr__(name, val)

File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:1116, in BlockData.add_component(self, name, val)
   1108     logger.debug(
   1109         "Constructing %s '%s' on %s from data=%s",
   1110         val.__class__.__name__,
   (...)
   1113         str(data),
   1114     )
   1115 try:
-> 1116     val.construct(data)
   1117 except:
   1118     err = sys.exc_info()[1]

File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:2173, in Block.construct(self, data)
   2171                     obj.construct(data.get(name, None))
   2172         # Trigger the (normal) initialization of the block
-> 2173         self._getitem_when_not_present(_idx)
   2174 finally:
   2175     # We must allow that id(self) may no longer be in
   2176     # _BlockConstruction.data, as _getitem_when_not_present will
   2177     # have already removed the entry for scalar blocks (as the
   2178     # BlockData and the Block component are the same object)
   2179     if data is not None:

File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:2088, in Block._getitem_when_not_present(self, idx)
   2085     data = None
   2087 try:
-> 2088     obj = self._rule(_block, idx)
   2089     # If the user returns a block, transfer over everything
   2090     # they defined into the empty one we created.  We do
   2091     # this inside the try block so that any abstract
   2092     # components declared by the rule have the opportunity
   2093     # to be initialized with data from
   2094     # _BlockConstruction.data as they are transferred over.
   2095     if obj is not _block and isinstance(obj, BlockData):

File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/initializer.py:316, in IndexedCallInitializer.__call__(self, parent, idx)
    314     return self._fcn(parent, *idx)
    315 else:
--> 316     return self._fcn(parent, idx)

File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py:41, in _rule_default(b, *args)
     35 """
     36 Default rule for ProcessBlock, which calls build(). A different rule can
     37 be specified to add additional build steps, or to not call build at all
     38 using the normal rule argument to ProcessBlock init.
     39 """
     40 try:
---> 41     b.build()
     42 except Exception:
     43     logging.getLogger(__name__).exception(f"Failure in build: {b}")

File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/models/properties/general_helmholtz/helmholtz_functions.py:1441, in HelmholtzParameterBlockData.build(self)
   1439 """Populate the parameter block"""
   1440 if not self.available():
-> 1441     raise RuntimeError("Helmholtz EoS external functions not available")
   1442 super().build()
   1443 # Check if the specified component is supported

RuntimeError: Helmholtz EoS external functions not available
# Create an instance of the heat exchanger unit, attaching it to the flowsheet
# Specify that the property package to be used with the heater is the one we created earlier.
m.fs.heat_exchanger = HeatExchanger(
    delta_temperature_callback=delta_temperature_amtd_callback,
    hot_side_name="shell",
    cold_side_name="tube",
    shell={"property_package": m.fs.properties_shell},
    tube={"property_package": m.fs.properties_tube},
)

# Call the degrees_of_freedom function, get initial DOF
DOF_initial = degrees_of_freedom(m)
print("The initial DOF is {0}".format(DOF_initial))
The initial DOF is 10
h = htpx(450 * units.K, P=101325 * units.Pa)

# Fix the inlet conditions
m.fs.heat_exchanger.shell_inlet.flow_mol.fix(100)  # mol/s
m.fs.heat_exchanger.shell_inlet.pressure.fix(101325)
m.fs.heat_exchanger.shell_inlet.enth_mol.fix(h)  # J/mol

DOF_initial = degrees_of_freedom(m)
print("The DOF is {0}".format(DOF_initial))
The DOF is 7
m.fs.heat_exchanger.tube_inlet.flow_mol.fix(250)  # mol/s
m.fs.heat_exchanger.tube_inlet.mole_frac_comp[0, "benzene"].fix(0.4)
m.fs.heat_exchanger.tube_inlet.mole_frac_comp[0, "toluene"].fix(0.6)
m.fs.heat_exchanger.tube_inlet.pressure.fix(101325)  # Pa
m.fs.heat_exchanger.tube_inlet.temperature[0].fix(350)  # K

# Call the degrees_of_freedom function, get final DOF
DOF_final = degrees_of_freedom(m)
print("The DOF is {0}".format(DOF_final))
The DOF is 2

Option 1: Fix overall HTC and the heat transfer area#

m.fs.heat_exchanger.area.fix(50)  # m2
m.fs.heat_exchanger.overall_heat_transfer_coefficient[0].fix(500)  # W/m2/K

# Call the degrees_of_freedom function, get final DOF
DOF_final = degrees_of_freedom(m)
print("The DOF is {0}".format(DOF_final))
The DOF is 0
# Initialize the flowsheet, and set the output at WARNING
m.fs.heat_exchanger.initialize(outlvl=idaeslog.INFO)

# Solve the simulation using ipopt
# Note: If the degrees of freedom = 0, we have a square problem
opt = SolverFactory("ipopt")
solve_status = opt.solve(m)

# Display a readable report
m.fs.heat_exchanger.report()
2023-11-02 10:25:29 [INFO] idaes.init.fs.heat_exchanger.hot_side: Initialization Complete
2023-11-02 10:25:29 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_in: Starting initialization
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_in: Dew and bubble point initialization: optimal - Optimal Solution Found.
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_in: Equilibrium temperature initialization completed.
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_in: State variable initialization completed.
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_in: Phase equilibrium initialization: optimal - Optimal Solution Found.
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_in: Property initialization: optimal - Optimal Solution Found.
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_out: Starting initialization
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_out: Dew and bubble point initialization: optimal - Optimal Solution Found.
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_out: Equilibrium temperature initialization completed.
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_out: State variable initialization completed.
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_out: Phase equilibrium initialization: optimal - Optimal Solution Found.
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side.properties_out: Property initialization: optimal - Optimal Solution Found.
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger.cold_side: Initialization Complete
2023-11-02 10:25:30 [INFO] idaes.init.fs.heat_exchanger: Initialization Completed, optimal - Optimal Solution Found
====================================================================================
Unit : fs.heat_exchanger                                                   Time: 0.0
------------------------------------------------------------------------------------
    Unit Performance

    Variables: 

    Key            : Value      : Units                           : Fixed : Bounds
           HX Area :     50.000 :                      meter ** 2 :  True : (0, None)
    HX Coefficient :     500.00 : kilogram / kelvin / second ** 3 :  True : (0, None)
         Heat Duty : 1.2985e+06 :                            watt : False : (None, None)

    Expressions: 

    Key             : Value  : Units
    Delta T Driving : 51.940 : kelvin
         Delta T In : 80.757 : kelvin
        Delta T Out : 23.124 : kelvin

------------------------------------------------------------------------------------
    Stream Table
                                      Units        shell Inlet shell Outlet tube Inlet tube Outlet
    Molar Flow                       mole / second       100        100.00           -           -
    Mass Flow                    kilogram / second    1.8015        1.8015           -           -
    T                                       kelvin    450.00        373.12           -           -
    P                                       pascal    101325    1.0132e+05           -           -
    Vapor Fraction                   dimensionless    1.0000       0.74888           -           -
    Molar Enthalpy                    joule / mole    50977.        37992.           -           -
    Total Molar Flowrate             mole / second         -             -         250      250.00
    Total Mole Fraction benzene      dimensionless         -             -     0.40000     0.40000
    Total Mole Fraction toluene      dimensionless         -             -     0.60000     0.60000
    Temperature                             kelvin         -             -         350      369.24
    Pressure                                pascal         -             -  1.0132e+05  1.0132e+05
====================================================================================

Option 2: Unfix area and fix shell side outlet temperature#

In the previous example, we fixed the heat exchanger area and overall heat transfer coefficient. However, given that the models in IDAES are equation oriented, we can fix the outlet variables. For example, we can fix the outlet temperature for the shell side and solve for the heat exchanger area that will satisfy that condition.

m.fs.heat_exchanger.area.unfix()
m.fs.heat_exchanger.shell_outlet.enth_mol.fix(htpx(360 * units.K, P=101325 * units.Pa))
print(degrees_of_freedom(m))
0
result = opt.solve(m)

print(result)

# Display a readable report
m.fs.heat_exchanger.report()
Problem: 
- Lower bound: -inf
  Upper bound: inf
  Number of objectives: 1
  Number of constraints: 44
  Number of variables: 44
  Sense: unknown
Solver: 
- Status: ok
  Message: Ipopt 3.13.2\x3a Optimal Solution Found
  Termination condition: optimal
  Id: 0
  Error rc: 0
  Time: 0.05035805702209473
Solution: 
- number of solutions: 0
  number of solutions displayed: 0


====================================================================================
Unit : fs.heat_exchanger                                                   Time: 0.0
------------------------------------------------------------------------------------
    Unit Performance

    Variables: 

    Key            : Value      : Units                           : Fixed : Bounds
           HX Area :     200.26 :                      meter ** 2 : False : (0, None)
    HX Coefficient :     500.00 : kilogram / kelvin / second ** 3 :  True : (0, None)
         Heat Duty : 4.4423e+06 :                            watt : False : (None, None)

    Expressions: 

    Key             : Value  : Units
    Delta T Driving : 44.365 : kelvin
         Delta T In : 78.730 : kelvin
        Delta T Out : 10.000 : kelvin

------------------------------------------------------------------------------------
    Stream Table
                                      Units        shell Inlet shell Outlet tube Inlet tube Outlet
    Molar Flow                       mole / second       100        100.00           -           -
    Mass Flow                    kilogram / second    1.8015        1.8015           -           -
    T                                       kelvin    450.00        360.00           -           -
    P                                       pascal    101325    1.0132e+05           -           -
    Vapor Fraction                   dimensionless    1.0000        0.0000           -           -
    Molar Enthalpy                    joule / mole    50977.        6554.3           -           -
    Total Molar Flowrate             mole / second         -             -         250      250.00
    Total Mole Fraction benzene      dimensionless         -             -     0.40000     0.40000
    Total Mole Fraction toluene      dimensionless         -             -     0.60000     0.60000
    Temperature                             kelvin         -             -         350      371.27
    Pressure                                pascal         -             -  1.0132e+05  1.0132e+05
====================================================================================