Heat Exchanger (Lumped Capacitance)

Author: Rusty Gentile

The HeatExchangerLumpedCapacitance model can be imported from idaes.models.unit_models. This model is an extension of idaes.models.unit_models.heat_exchanger, with wall temperature and heat holdup terms added for use in transient simulations. Using the electric circuit analogy, heat stored in the wall material is similar to charge in a capacitor.

Degrees of Freedom

HeatExchangerLumpedCapacitance has three degrees of freedom. For most scenarios, these will be the wall temperature and exit temperatures on the hot and cold sides. The constraints for this model should be similar to those of the standard Heat Exchanger model. In lieu of “heat_transfer_coefficient”, however, the following should be fixed:

  • ua_cold_side

  • ua_hot_side

The user may also add constraints to make these functions of fluid state.

Model Structure

Structure is derived from the base 0D Heat Exchanger model. Aside from adding new variables and constraints, the structure of this model is unchanged.

Variables

All variables from the base 0D Heat Exchanger model are included. This model contains the following variables in addition. Each of these is indexed in the time domain.

Variable

Symbol

Doc

temperature_wall

\(T_{wall}\)

Average wall temperature

dT_wall_dt

\(\frac{dT_{wall}}{dt}\)

Derivative of wall temperature with respect to time

ua_cold_side

\(UA_{cold}\)

Overall heat transfer coefficient from the cold side

ua_hot_side

\(UA_{hot}\)

Overall heat transfer coefficient from the hot side

ua_hot_side_to_wall

\(UA_{hot, wall}\)

Overall heat transfer coefficient between the hot side and the center of the wall material

hot_side_heat

\(Q_{hot}\)

Heat entering the hot side control volume (value is typically negative)

cold_side_heat

\(Q_{cold}\)

Heat entering the cold side control volume (value is typically positive)

Note: “ua” variables are of the form \(hconv \times area\). So the units of measure of these should be \(\frac{energy}{time \times temperature}\)

Parameters

The model uses the following parameters. These are not indexed as we assume they are constant.

Parameter

Symbol

Doc

heat_capacity_wall

\(C_{wall}\)

Total heat capacity of heat exchanger material

thermal_resistance_wall

\(R_{wall}\)

Total thermal resistance of heat exchanger material

thermal_fouling_cold_side

\(R_{foul, cold}\)

Total thermal resistance due to fouling on the cold side

thermal_fouling_hot_side

\(R_{foul, hot}\)

Total thermal resistance due to fouling on the hot side

Constraints

Note: all constraints from the base 0D Heat Exchanger model are also included.

Total overall heat transfer coefficient:

\[\frac{1}{UA} = \frac{1}{UA_{hot}} + R_{foul, hot} + R_{wall} + R_{foul, cold} + \frac{1}{UA_{cold}}\]

Overall heat transfer coefficient from the hot side to the center of the wall:

\[\frac{1}{UA_{hot, wall}} = \frac{1}{UA_{hot}} + R_{foul, hot} + \frac{1}{2}R_{wall}\]

Wall temperature equation:

\[T_{wall} = \frac{1}{2}(T_{hot, in} + T_{hot, out}) + \frac{Q_{hot}}{UA_{hot, wall}}\]

Dynamic heat balance (if dynamic_heat_balance is set to True):

\[Q_{hot} + Q_{cold} + \frac{dT_{wall}}{dt}C_{wall} = 0\]

Standard heat balance (if dynamic_heat_balance is set to False):

\[Q_{hot} + Q_{cold} = 0\]

Example

In this example, we run a transient simulation of an air-cooled SCO2 heat exchanger using a dynamic heat balance. The control volumes, however, are static. This is essentially an assumption that any mass holdup of the working fluid is negligible.

To use dynamic control volumes, constraints should be added that relate flow rate to pressure loss. In that case, the volume variables of the hot and cold sides should also be fixed.

import pyomo.environ as pe
from idaes.core import FlowsheetBlock
from idaes.models.unit_models import HeatExchangerLumpedCapacitance, HeatExchangerFlowPattern
from idaes.models.unit_models.heat_exchanger import delta_temperature_lmtd_callback
from idaes.models.properties import swco2
from idaes.models_extra.power_generation.properties import FlueGasParameterBlock
from idaes.core.util.plot import plot_grid_dynamic

m = pe.ConcreteModel()
m.fs = FlowsheetBlock(
    dynamic=True,
    time_set=[0, 300, 600, 900, 1200, 1500],
    time_units=pe.units.s
)

m.fs.prop_sco2 = swco2.SWCO2ParameterBlock()
m.fs.prop_fluegas = FlueGasParameterBlock()

m.fs.HE = HeatExchangerLumpedCapacitance(
    delta_temperature_callback=delta_temperature_lmtd_callback,
    cold_side_name="shell",
    hot_side_name="tube",
    shell={"property_package": m.fs.prop_fluegas, "has_pressure_change": False},
    tube={"property_package": m.fs.prop_sco2, "has_pressure_change": True},
    flow_pattern=HeatExchangerFlowPattern.crossflow,
    dynamic_heat_balance=True,
    dynamic=False
)

m.discretizer = pe.TransformationFactory('dae.finite_difference')
m.discretizer.apply_to(m, nfe=200, wrt=m.fs.time, scheme="BACKWARD")

# Cold-side boundary conditions
shell_inlet_temperature = 288.15
shell_flow = 44004.14222
shell_area = 690073.9153
shell_hconv = 22
m.fs.HE.ua_cold_side[:].fix(shell_area * shell_hconv)
m.fs.HE.shell_inlet.flow_mol_comp[:, "H2O"].fix(0.01027 * shell_flow)
m.fs.HE.shell_inlet.flow_mol_comp[:, "CO2"].fix(0.000411592 * shell_flow)
m.fs.HE.shell_inlet.flow_mol_comp[:, "N2"].fix(0.780066026 * shell_flow)
m.fs.HE.shell_inlet.flow_mol_comp[:, "O2"].fix(0.209252382 * shell_flow)
m.fs.HE.shell_inlet.flow_mol_comp[:, "NO"].fix(0)
m.fs.HE.shell_inlet.flow_mol_comp[:, "SO2"].fix(0)
m.fs.HE.shell_inlet.temperature[:].fix(shell_inlet_temperature)
m.fs.HE.shell_inlet.pressure[:].fix(101325)

# Hot-side boundary conditions
tube_inlet_temperature = 384.35
tube_inlet_pressure = 7653000
tube_outlet_pressure = 7500000
tube_inlet_enthalpy = swco2.htpx(T=tube_inlet_temperature * pe.units.K,
                                 P=tube_inlet_pressure * pe.units.Pa)
tube_flow = 13896.84163
tube_area = 19542.2771
tube_hconv = 1000
m.fs.HE.ua_hot_side[:].fix(tube_area * tube_hconv)
m.fs.HE.tube_inlet.flow_mol[:].fix(tube_flow)
m.fs.HE.tube_inlet.pressure[:].fix(tube_inlet_pressure)
m.fs.HE.tube_inlet.enth_mol[:].fix(tube_inlet_enthalpy)
m.fs.HE.tube_outlet.pressure[:].fix(tube_outlet_pressure)

# number of tubes * tube mass * specific heat capacity
m.fs.HE.heat_capacity_wall = 1160 * 322 * 466
m.fs.HE.crossflow_factor.fix(0.8)

# Area has no effect on the result so long as it isn't zero
m.fs.HE.area.fix(1)

# Initialize the model with steady-state boundary conditions
m.fs.HE.initialize()
solver = pe.SolverFactory('ipopt')

# Activate the heat holdup term and add temperature disturbances
m.fs.HE.activate_dynamic_heat_eq()
for t in m.fs.time:
    if 300 <= t < 600:
        m.fs.HE.shell_inlet.temperature[t].fix(288.15 - 10)
    elif 600 <= t < 900:
        m.fs.HE.shell_inlet.temperature[t].fix(288.15)
    elif 900 <= t < 1200:
        m.fs.HE.shell_inlet.temperature[t].fix(288.15 + 10)
    elif t >= 1200:
        m.fs.HE.shell_inlet.temperature[t].fix(288.15)

solver.solve(m)

# Plot some results and save the figure to a file.
plot_grid_dynamic(
    x=m.fs.time,
    xlabel="time (s)",
    y=[
        m.fs.HE.tube.properties_out[:].temperature,
        m.fs.HE.tube.properties_out[:].dens_mass,
        m.fs.HE.temperature_wall[:],
        m.fs.HE.shell.properties_in[:].temperature
    ],
    ylabel=[
        "sCO2 out (K)",
        "sCO2 out (kg/m^3)",
        "wall temperature (K)",
        "air in (K)"
    ],
    yunits=[
        pe.units.K,
        pe.units.kg / pe.units.m ** 3,
        pe.units.K,
        pe.units.K
    ],
    cols=2,
    rows=2,
    to_file="transient_sco2.pdf"
)

Class Documentation

class idaes.models.unit_models.heat_exchanger_lc.HeatExchangerLumpedCapacitance(*args, **kwds)

0D heat exchanger for transient simulations

Parameters
  • rule (function) – A rule function or None. Default rule calls build().

  • concrete (bool) – If True, make this a toplevel model. Default - False.

  • ctype (class) – Pyomo ctype of the block. Default - pyomo.environ.Block

  • default (dict) –

    Default ProcessBlockData config

    Keys
    dynamic

    Indicates whether this model will be dynamic or not, default = useDefault. Valid values: { useDefault - get flag from parent (default = False), True - set as a dynamic model, False - set as a steady-state model.}

    has_holdup

    Indicates whether holdup terms should be constructed or not. Must be True if dynamic = True, default - False. Valid values: { useDefault - get flag from parent (default = False), True - construct holdup terms, False - do not construct holdup terms}

    hot_side_name

    Hot side name, sets control volume and inlet and outlet names

    cold_side_name

    Cold side name, sets control volume and inlet and outlet names

    hot_side_config

    A config block used to construct the hot side control volume. This config can be given by the hot side name instead of hot_side_config.

    material_balance_type

    Indicates what type of mass balance should be constructed, default - MaterialBalanceType.useDefault. Valid values: { MaterialBalanceType.useDefault - refer to property package for default balance type **MaterialBalanceType.none - exclude material balances, MaterialBalanceType.componentPhase - use phase component balances, MaterialBalanceType.componentTotal - use total component balances, MaterialBalanceType.elementTotal - use total element balances, MaterialBalanceType.total - use total material balance.}

    energy_balance_type

    Indicates what type of energy balance should be constructed, default - EnergyBalanceType.useDefault. Valid values: { EnergyBalanceType.useDefault - refer to property package for default balance type **EnergyBalanceType.none - exclude energy balances, EnergyBalanceType.enthalpyTotal - single enthalpy balance for material, EnergyBalanceType.enthalpyPhase - enthalpy balances for each phase, EnergyBalanceType.energyTotal - single energy balance for material, EnergyBalanceType.energyPhase - energy balances for each phase.}

    momentum_balance_type

    Indicates what type of momentum balance should be constructed, default - MomentumBalanceType.pressureTotal. Valid values: { MomentumBalanceType.none - exclude momentum balances, MomentumBalanceType.pressureTotal - single pressure balance for material, MomentumBalanceType.pressurePhase - pressure balances for each phase, MomentumBalanceType.momentumTotal - single momentum balance for material, MomentumBalanceType.momentumPhase - momentum balances for each phase.}

    has_phase_equilibrium

    Indicates whether terms for phase equilibrium should be constructed, default = False. Valid values: { True - include phase equilibrium terms False - exclude phase equilibrium terms.}

    has_pressure_change

    Indicates whether terms for pressure change should be constructed, default - False. Valid values: { True - include pressure change terms, False - exclude pressure change terms.}

    property_package

    Property parameter object used to define property calculations, default - useDefault. Valid values: { useDefault - use default package from parent model or flowsheet, PropertyParameterObject - a PropertyParameterBlock object.}

    property_package_args

    A ConfigBlock with arguments to be passed to a property block(s) and used when constructing these, default - None. Valid values: { see property package for documentation.}

    cold_side_config

    A config block used to construct the cold side control volume. This config can be given by the cold side name instead of cold_side_config.

    material_balance_type

    Indicates what type of mass balance should be constructed, default - MaterialBalanceType.useDefault. Valid values: { MaterialBalanceType.useDefault - refer to property package for default balance type **MaterialBalanceType.none - exclude material balances, MaterialBalanceType.componentPhase - use phase component balances, MaterialBalanceType.componentTotal - use total component balances, MaterialBalanceType.elementTotal - use total element balances, MaterialBalanceType.total - use total material balance.}

    energy_balance_type

    Indicates what type of energy balance should be constructed, default - EnergyBalanceType.useDefault. Valid values: { EnergyBalanceType.useDefault - refer to property package for default balance type **EnergyBalanceType.none - exclude energy balances, EnergyBalanceType.enthalpyTotal - single enthalpy balance for material, EnergyBalanceType.enthalpyPhase - enthalpy balances for each phase, EnergyBalanceType.energyTotal - single energy balance for material, EnergyBalanceType.energyPhase - energy balances for each phase.}

    momentum_balance_type

    Indicates what type of momentum balance should be constructed, default - MomentumBalanceType.pressureTotal. Valid values: { MomentumBalanceType.none - exclude momentum balances, MomentumBalanceType.pressureTotal - single pressure balance for material, MomentumBalanceType.pressurePhase - pressure balances for each phase, MomentumBalanceType.momentumTotal - single momentum balance for material, MomentumBalanceType.momentumPhase - momentum balances for each phase.}

    has_phase_equilibrium

    Indicates whether terms for phase equilibrium should be constructed, default = False. Valid values: { True - include phase equilibrium terms False - exclude phase equilibrium terms.}

    has_pressure_change

    Indicates whether terms for pressure change should be constructed, default - False. Valid values: { True - include pressure change terms, False - exclude pressure change terms.}

    property_package

    Property parameter object used to define property calculations, default - useDefault. Valid values: { useDefault - use default package from parent model or flowsheet, PropertyParameterObject - a PropertyParameterBlock object.}

    property_package_args

    A ConfigBlock with arguments to be passed to a property block(s) and used when constructing these, default - None. Valid values: { see property package for documentation.}

    delta_temperature_callback

    Callback for for temperature difference calculations

    flow_pattern

    Heat exchanger flow pattern, default - HeatExchangerFlowPattern.countercurrent. Valid values: { HeatExchangerFlowPattern.countercurrent - countercurrent flow, HeatExchangerFlowPattern.cocurrent - cocurrent flow, HeatExchangerFlowPattern.crossflow - cross flow, factor times countercurrent temperature difference.}

    side_1_is_hot

    If True, side_1 is an alias for hot_side. Otherwise, side_1 is an alias for cold_side

    dynamic_heat_balance

    Indicates whether heat holdup in the wall material should be included in the overall energy balance, default - useDefault. Valid values: { useDefault - get flag from parent (default = False), True - include wall material heat holdup, False - do not include wall material heat holdup.}

  • initialize (dict) – ProcessBlockData config for individual elements. Keys are BlockData indexes and values are dictionaries described under the “default” argument above.

  • idx_map (function) – Function to take the index of a BlockData element and return the index in the initialize dict from which to read arguments. This can be provided to overide the default behavior of matching the BlockData index exactly to the index in initialize.

Returns

(HeatExchangerLumpedCapacitance) New instance

class idaes.models.unit_models.heat_exchanger_lc.HeatExchangerLumpedCapacitanceData(component)[source]
activate_dynamic_heat_eq()[source]

Activates the heat holdup term in the overall energy balance for transient simulations. Should only be used with dynamic flowsheets and if dynamic_heat_balance is True.

Parameters

None

Returns

None

build()[source]

Building model

Parameters

None

Returns

None

deactivate_dynamic_heat_eq()[source]

Removes the heat holdup term from the overall energy balance, restoring the behavior of the base 0D HeatExchanger model. This may be useful for more complex models which need to be solved in several stages.

Parameters

None

Returns

None

initialize(*args, **kwargs)[source]

Initialization routine for Unit Model objects and associated components.

This method is intended for initializing IDAES unit models and any modeling components attached to them, such as costing blocks. This method iterates through all objects in blk._initialization_order and deactivates them, followed by calling blk.initialize_build. Finally, it iterates through all objects in blk._initialization_order in reverse and re-activates these whilst calling the associated initialize method.

Currently, parsing of arguments to the initialize method of attached blocks is hard coded - this will be addressed in a future PR. Currently, the method supports the following attached components:

  • UnitModelCostingBlocks

Parameters

block (costing_args - dict arguments to be passed to costing) – initialize method

For other arguments, see the initilize_unit method.