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:
Overall heat transfer coefficient from the hot side to the center of the wall:
Wall temperature equation:
Dynamic heat balance (if dynamic_heat_balance
is set to True
):
Standard heat balance (if dynamic_heat_balance
is set to False
):
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
Config args
- 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
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.
- hot_side
- 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
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.
- cold_side
- 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.}
- 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 with config arguments as keys.
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 override 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]#
Lumped capacitance HX unit class.
- 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
- 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 initialize_unit method.