##############################################################################
# Institute for the Design of Advanced Energy Systems Process Systems
# Engineering Framework (IDAES PSE Framework) Copyright (c) 2018-2019, 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.txt and LICENSE.txt for full copyright and
# license information, respectively. Both files are also available online
# at the URL "https://github.com/IDAES/idaes-pse".
##############################################################################
"""
IDAES PLATE HEAT EXCHANGER MODEL (PHE) USING EFFECTIVENESS-NTU METHOD
Notes:
Let P = number of passes(same for both fluids)
if P is even PHE is in counter-current mode
if P is odd PHE is in co-current mode
Hot and cold fluids flow alternatively in the channels.
Divider plates may be used to partition the passes in separate sections.
The heat transfer area of a single plate depends on the total heat transfer area
as specified by the manufacturer and the total number of active plates.
Detailed model equations can be found in the paper :
Akula, P., Eslick, J., Bhattacharyya, D. and Miller, D.C., 2019.
Modelling and Parameter Estimation of a Plate Heat Exchanger
as Part of a Solvent-Based Post-Combustion CO2 Capture System.
In Computer Aided Chemical Engineering (Vol. 47, pp. 47-52). Elsevier.
"""
# Import Pyomo libraries
from pyomo.environ import Param, RangeSet, Constraint, Expression,\
SolverFactory, value, Var, exp, units as pyunits
from pyomo.common.config import ConfigBlock, ConfigValue
# Import IDAES cores
from idaes.core import (ControlVolume0DBlock,
declare_process_block_class,
MaterialBalanceType,
MomentumBalanceType,
UnitModelBlockData,
useDefault)
from idaes.core.util.config import is_physical_parameter_block
from idaes.core.util.exceptions import ConfigurationError
from idaes.core.util import get_solver
import idaes.logger as idaeslog
__author__ = "Paul Akula"
# Set up logger
_log = idaeslog.getLogger(__name__)
[docs]@declare_process_block_class("PHE")
class PHEData(UnitModelBlockData):
"""Plate Heat Exchanger(PHE) Unit Model."""
CONFIG = UnitModelBlockData.CONFIG()
# Configuration template for fluid specific arguments
_SideCONFIG = ConfigBlock()
CONFIG.declare("passes", ConfigValue(
default=4,
domain=int,
description="Number of passes",
doc="""Number of passes of the fluids through the heat exchanger"""))
CONFIG.declare("channel_list", ConfigValue(
default=[12, 12, 12, 12],
domain=list,
description="Number of channels for each pass",
doc="""Number of channels to be used in each pass where a channel
is the space between two plates with a flowing fluid"""))
CONFIG.declare("divider_plate_number", ConfigValue(
default=0,
domain=int,
description="Number of divider plates in heat exchanger",
doc="""Divider plates are used to create separate partitions in the unit.
Each pass can be separated by a divider plate"""))
CONFIG.declare("port_diameter", ConfigValue(
default=0.2045,
domain=float,
description="Diameter of the ports on the plate [m]",
doc="""Diameter of the ports on the plate for fluid entry/exit
into a channel"""))
CONFIG.declare("plate_thermal_cond", ConfigValue(
default=16.2,
domain=float,
description="Thermal conductivity [W/m.K]",
doc="""Thermal conductivity of the plate material [W/m.K]"""))
CONFIG.declare("total_area", ConfigValue(
default=114.3,
domain=float,
description="Total heat transfer area [m2]",
doc="""Total heat transfer area as specifed by the manufacturer"""))
CONFIG.declare("plate_thickness", ConfigValue(
default=0.0006,
domain=float,
description="Plate thickness [m]",
doc="""Plate thickness"""))
CONFIG.declare("plate_vertical_dist", ConfigValue(
default=1.897,
domain=float,
description="Vertical distance between centers of ports [m].",
doc="""Vertical distance between centers of ports.(Top and bottom ports)
(approximately equals to the plate length)"""))
CONFIG.declare("plate_horizontal_dist", ConfigValue(
default=0.409,
domain=float,
description="Horizontal distance between centers of ports [m].",
doc="""Horizontal distance between centers of ports(Left and right ports)"""))
CONFIG.declare("plate_pact_length", ConfigValue(
default=0.381,
domain=float,
description="Compressed plate pact length [m].",
doc="""Compressed plate pact length.
Length between the Head and the Follower"""))
CONFIG.declare("surface_enlargement_factor", ConfigValue(
default=None,
domain=float,
description="Surface enlargement factor",
doc="""Surface enlargement factor is the ratio of single plate area
(obtained from the total area) to the projected plate area"""))
CONFIG.declare("plate_gap", ConfigValue(
default=None,
domain=float,
description="Mean channel spacing or gap bewteen two plates [m]",
doc="""The plate gap is the distance between two adjacent plates that
forms a flow channel """))
_SideCONFIG.declare("property_package", ConfigValue(
default=useDefault,
domain=is_physical_parameter_block,
description="Property package to use ",
doc="""Property parameter object used to define property calculations
**default** - useDefault.
**Valid values:** {
**useDefault** - use default package from parent model or flowsheet,
**PhysicalParameterObject** - a PhysicalParameterBlock object.}"""))
_SideCONFIG.declare("property_package_args", ConfigBlock(
implicit=True,
description="Arguments to use for constructing property package",
doc="""A ConfigBlock with arguments to be passed to
property block(s) and used when constructing these,
**default** - None.
**Valid values:** {
see property package for documentation.}"""))
# Create individual config blocks for hot and cold sides
CONFIG.declare("hot_side",
_SideCONFIG(doc="Hot fluid config arguments"))
CONFIG.declare("cold_side",
_SideCONFIG(doc="Cold fluid config arguments"))
[docs] def build(self):
# Call UnitModel.build to setup model
super(PHEData, self).build()
# Consistency check for number of passes and channels in each pass
for i in self.config.channel_list:
if not isinstance(i, int):
raise ConfigurationError("number of channels ({}) must be"
" an integer".format(i))
if (self.config.passes != len(self.config.channel_list)):
raise ConfigurationError(
"The number of elements in the channel list: {} "
" does not match the number of passes ({}) given. "
"Please provide as integers, the number of channels of each pass".format(
self.config.channel_list, self.config.passes))
# ======================================================================
# Build hot-side Control Volume (Lean Solvent)
self.hot_side = ControlVolume0DBlock(default={
"dynamic": self.config.dynamic,
"has_holdup": self.config.has_holdup,
"property_package": self.config.hot_side.property_package,
"property_package_args": self.config.hot_side.property_package_args})
self.hot_side.add_state_blocks(has_phase_equilibrium=False)
self.hot_side.add_material_balances(
balance_type=MaterialBalanceType.componentTotal,
has_mass_transfer=False,
has_phase_equilibrium=False,
has_rate_reactions=False)
self.hot_side.add_momentum_balances(
balance_type=MomentumBalanceType.pressureTotal,
has_pressure_change=True)
# Energy balance is based on the effectiveness Number of Transfer units
# (E-NTU method) and inluded as performance equations. Hence the control
# volume energy balances are not added.
# ======================================================================
# Build cold-side Control Volume(Rich solvent)
self.cold_side = ControlVolume0DBlock(default={
"dynamic": self.config.dynamic,
"has_holdup": self.config.has_holdup,
"property_package": self.config.cold_side.property_package,
"property_package_args": self.config.cold_side.property_package_args})
self.cold_side.add_state_blocks(has_phase_equilibrium=False)
self.cold_side.add_material_balances(
balance_type=MaterialBalanceType.componentTotal,
has_mass_transfer=False,
has_phase_equilibrium=False,
has_rate_reactions=False)
self.cold_side.add_momentum_balances(
balance_type=MomentumBalanceType.pressureTotal,
has_pressure_change=True)
# ======================================================================
# Add Ports to control volumes
# hot-side
self.add_inlet_port(name="hot_inlet",
block=self.hot_side, doc='inlet Port')
self.add_outlet_port(name="hot_outlet",
block=self.hot_side, doc='outlet Port')
# cold-side
self.add_inlet_port(name="cold_inlet",
block=self.cold_side, doc='inlet Port')
self.add_outlet_port(name="cold_outlet",
block=self.cold_side, doc='outlet Port')
# ======================================================================
# Add performace equation method
self._make_params()
self._make_performance_method()
def _make_params(self):
self.P = Param(initialize=self.config.passes,
units=None,
doc="Total number of passes for hot or cold fluid")
self.PH = RangeSet(self.P,
doc="Set of hot fluid passes")
self.PC = RangeSet(self.P,
doc="Set of cold fluid passes(equal to PH)")
self.plate_thermal_cond = Param(mutable=True,
initialize=self.config.plate_thermal_cond,
units=pyunits.W / pyunits.m / pyunits.K,
doc="Plate thermal conductivity")
self.plate_thick = Param(mutable=True,
initialize=self.config.plate_thickness,
units=pyunits.m,
doc="Plate thickness")
self.port_dia = Param(mutable=True,
initialize=self.config.port_diameter,
units=pyunits.m,
doc=" Port diameter of plate ")
self.Np = Param(self.PH,
units=None,
doc="Number of channels in each pass", mutable=True)
# Number of channels in each pass
for i in self.PH:
self.Np[i].value = self.config.channel_list[i - 1]
# ---------------------------------------------------------------------
# Assign plate specifications
# effective plate length & width
_effective_plate_length = self.config.plate_vertical_dist - \
self.config.port_diameter
_effective_plate_width = self.config.plate_horizontal_dist + \
self.config.port_diameter
self.plate_length = Expression(expr=_effective_plate_length)
self.plate_width = Expression(expr=_effective_plate_width)
# Area of single plate
_total_active_plate_number = 2 * sum(self.config.channel_list) - 1 -\
self.config.divider_plate_number
self.plate_area = Expression(expr=self.config.total_area /
_total_active_plate_number,
doc="Heat transfer area of single plate")
# Plate gap
if self.config.plate_gap is None:
_total_plate_number = 2 * sum(self.config.channel_list) + 1 +\
self.config.divider_plate_number
_plate_pitch = self.config.plate_pact_length / _total_plate_number
_plate_gap = _plate_pitch - self.config.plate_thickness
else:
_plate_gap = self.config.plate_gap
self.plate_gap = Expression(expr=_plate_gap)
# Surface enlargement factor
if self.config.surface_enlargement_factor is None:
_projected_plate_area = _effective_plate_length * _effective_plate_width
_surface_enlargement_factor = self.plate_area / _projected_plate_area
else:
_surface_enlargement_factor = self.config.surface_enlargement_factor
self.surface_enlargement_factor = Expression(expr=_surface_enlargement_factor)
# Channel equivalent diameter
self.channel_dia = Expression(expr=2 * self.plate_gap /
_surface_enlargement_factor ,
doc=" Channel equivalent diameter")
# heat transfer parameters
self.param_a = Var(initialize=0.3, bounds=(0.2, 0.4), units=None,
doc='Nusselt parameter')
self.param_b = Var(initialize=0.663, bounds=(0.3, 0.7), units=None,
doc='Nusselt parameter')
self.param_c = Var(initialize=1 / 3.0, bounds=(1e-5, 2), units=None,
doc='Nusselt parameter')
self.param_a.fix(0.4)
self.param_b.fix(0.663)
self.param_c.fix(0.333)
def _make_performance_method(self):
solvent_list = self.config.hot_side.property_package.component_list_solvent
def rule_trh(blk, t):
return (blk.hot_side.properties_out[t].temperature /
blk.hot_side.properties_in[t].temperature)
self.trh = Expression(self.flowsheet().config.time, rule=rule_trh,
doc='Ratio of hot outlet temperature to hot'
'inlet temperature')
def rule_trc(blk, t):
return (blk.cold_side.properties_out[t].temperature /
blk.cold_side.properties_in[t].temperature)
self.trc = Expression(self.flowsheet().config.time, rule=rule_trc,
doc='Ratio of cold outlet temperature to cold'
' inlet temperature')
def rule_cp_comp_hot(blk, t, j):
return 1e3 * (
blk.hot_side.properties_in[t]._params.cp_param[j, 1] +
blk.hot_side.properties_in[t]._params.cp_param[j, 2] / 2 *
blk.hot_side.properties_in[t].temperature *
(blk.trh[t] + 1) +
blk.hot_side.properties_in[t]._params.cp_param[j, 3] / 3 *
(blk.hot_side.properties_in[t].temperature**2) *
(blk.trh[t]**2 + blk.trh[t] + 1) +
blk.hot_side.properties_in[t]._params.cp_param[j, 4] / 4 *
(blk.hot_side.properties_in[t].temperature**3) *
(blk.trh[t] + 1) * (blk.trh[t]**2 + 1) +
blk.hot_side.properties_in[t]._params.cp_param[j, 5] / 5 *
(blk.hot_side.properties_in[t].temperature**4) *
(blk.trh[t]**4 + blk.trh[t]**3 + blk.trh[t]**2 + blk.trh[t] + 1))
self.cp_comp_hot = Expression(self.flowsheet().config.time,
solvent_list,
rule=rule_cp_comp_hot,
doc='Component mean specific heat capacity'
' btw inlet and outlet'
' of hot-side temperature')
def rule_cp_hot(blk, t):
return sum(blk.cp_comp_hot[t, j] *
blk.hot_side.properties_in[t].mass_frac_co2_free[j]
for j in solvent_list)
self.cp_hot = Expression(self.flowsheet().config.time, rule=rule_cp_hot,
doc='Hot-side mean specific heat capacity on'
'free CO2 basis')
def rule_cp_comp_cold(blk, t, j):
return 1e3 * (
blk.cold_side.properties_in[t]._params.cp_param[j, 1] +
blk.cold_side.properties_in[t]._params.cp_param[j, 2] / 2 *
blk.cold_side.properties_in[t].temperature *
(blk.trc[t] + 1) +
blk.cold_side.properties_in[t]._params.cp_param[j, 3] / 3 *
(blk.cold_side.properties_in[t].temperature**2) *
(blk.trc[t]**2 + blk.trc[t] + 1) +
blk.cold_side.properties_in[t]._params.cp_param[j, 4] / 4 *
(blk.cold_side.properties_in[t].temperature**3) *
(blk.trc[t] + 1) * (blk.trc[t]**2 + 1) +
blk.cold_side.properties_in[t]._params.cp_param[j, 5] / 5 *
(blk.cold_side.properties_in[t].temperature**4) *
(blk.trc[t]**4 + blk.trc[t]**3 + blk.trc[t]**2 + blk.trc[t] + 1))
self.cp_comp_cold = Expression(self.flowsheet().config.time,
solvent_list,
rule=rule_cp_comp_cold,
doc='Component mean specific heat capacity'
'btw inlet and outlet'
' of cold-side temperature')
def rule_cp_cold(blk, t):
return sum(blk.cp_comp_cold[t, j] *
blk.cold_side.properties_in[t].mass_frac_co2_free[j]
for j in solvent_list)
self.cp_cold = Expression(self.flowsheet().config.time, rule=rule_cp_cold,
doc='Cold-side mean specific heat capacity'
'on free CO2 basis')
# Model Variables
self.Th_in = Var(self.flowsheet().config.time,
self.PH, initialize=393, units=pyunits.K,
doc="Hot Temperature IN of pass")
self.Th_out = Var(self.flowsheet().config.time,
self.PH, initialize=325, units=pyunits.K,
doc="Hot Temperature OUT of pass")
self.Tc_in = Var(self.flowsheet().config.time,
self.PH, initialize=320, units=pyunits.K,
doc="Cold Temperature IN of pass")
self.Tc_out = Var(self.flowsheet().config.time,
self.PH, initialize=390, units=pyunits.K,
doc="Cold Temperature OUT of pass")
# ======================================================================
# PERFORMANCE EQUATIONS
# mass flow rate in kg/s
def rule_mh_in(blk, t):
return blk.hot_side.properties_in[t].flow_mol *\
blk.hot_side.properties_in[t].mw
self.mh_in = Expression(self.flowsheet().config.time, rule=rule_mh_in,
doc='Hotside mass flow rate [kg/s]')
def rule_mc_in(blk, t):
return blk.cold_side.properties_in[t].flow_mol *\
blk.cold_side.properties_in[t].mw
self.mc_in = Expression(self.flowsheet().config.time, rule=rule_mc_in,
doc='Coldside mass flow rate [kg/s]')
# ----------------------------------------------------------------------
# port mass velocity[kg/m2.s]
def rule_Gph(blk, t):
return (4 * blk.mh_in[t] * 7) / (22 * blk.port_dia**2)
self.Gph = Expression(self.flowsheet().config.time, rule=rule_Gph,
doc='Hotside port mass velocity[kg/m2.s]')
def rule_Gpc(blk, t):
return (4 * blk.mc_in[t] * 7) / (22 * blk.port_dia**2)
self.Gpc = Expression(self.flowsheet().config.time, rule=rule_Gpc,
doc='Coldside port mass velocity[kg/m2.s]')
# ----------------------------------------------------------------------
# Reynold & Prandtl numbers
def rule_Re_h(blk, t, p):
return blk.mh_in[t] * blk.channel_dia /\
(blk.Np[p] * blk.plate_width *
blk.plate_gap * blk.hot_side.properties_in[t].visc_d)
self.Re_h = Expression(self.flowsheet().config.time, self.PH,
rule=rule_Re_h,
doc='Hotside Reynolds number')
def rule_Re_c(blk, t, p):
return blk.mc_in[t] * blk.channel_dia /\
(blk.Np[p] * blk.plate_width *
blk.plate_gap * blk.cold_side.properties_in[t].visc_d)
self.Re_c = Expression(self.flowsheet().config.time, self.PH,
rule=rule_Re_c,
doc='Coldside Reynolds number')
def rule_Pr_h(blk, t):
return blk.cp_hot[t] * blk.hot_side.properties_in[t].visc_d /\
blk.hot_side.properties_in[t].thermal_cond
self.Pr_h = Expression(self.flowsheet().config.time,
rule=rule_Pr_h,
doc='Hotside Prandtl number')
def rule_Pr_c(blk, t):
return blk.cp_cold[t] * blk.cold_side.properties_in[t].visc_d /\
blk.cold_side.properties_in[t].thermal_cond
self.Pr_c = Expression(self.flowsheet().config.time,
rule=rule_Pr_c,
doc='Coldside Prandtl number')
# ----------------------------------------------------------------------
# Film heat transfer coefficients
def rule_hotside_transfer_coef(blk, t, p):
return (blk.hot_side.properties_in[t].thermal_cond / blk.channel_dia *
blk.param_a * blk.Re_h[t, p]**blk.param_b *
blk.Pr_h[t]**blk.param_c)
self.h_hot = Expression(self.flowsheet().config.time,
self.PH, rule=rule_hotside_transfer_coef,
doc='Hotside heat transfer coefficient')
def rule_coldside_transfer_coef(blk, t, p):
return (blk.cold_side.properties_in[t].thermal_cond / blk.channel_dia *
blk.param_a * blk.Re_c[t, p]**blk.param_b *
blk.Pr_c[t]**blk.param_c)
self.h_cold = Expression(self.flowsheet().config.time,
self.PH, rule=rule_coldside_transfer_coef,
doc='Coldside heat transfer coefficient')
# ----------------------------------------------------------------------
# Friction factor calculation
def rule_fric_h(blk, t):
return 18.29 * blk.Re_h[t, 1]**(-0.652)
self.fric_h = Expression(self.flowsheet().config.time,
rule=rule_fric_h,
doc='Hotside friction factor')
def rule_fric_c(blk, t):
return 1.441 * self.Re_c[t, 1]**(-0.206)
self.fric_c = Expression(self.flowsheet().config.time,
rule=rule_fric_c,
doc='Coldside friction factor')
# ----------------------------------------------------------------------
# pressure drop calculation
def rule_hotside_dP(blk, t):
return (2 * blk.fric_h[t] * (blk.plate_length + blk.port_dia) *
blk.P * blk.Gph[t]**2) /\
(blk.hot_side.properties_in[t].dens_mass *
blk.channel_dia) + 1.4 * blk.P * blk.Gph[t]**2 * 0.5 /\
blk.hot_side.properties_in[t].dens_mass + \
blk.hot_side.properties_in[t].dens_mass * \
9.81 * (blk.plate_length + blk.port_dia)
self.dP_h = Expression(self.flowsheet().config.time,
rule=rule_hotside_dP,
doc='Hotside pressure drop [Pa]')
def rule_coldside_dP(blk, t):
return (2 * blk.fric_c[t] * (blk.plate_length + blk.port_dia) *
blk.P * blk.Gpc[t]**2) /\
(blk.cold_side.properties_in[t].dens_mass * blk.channel_dia) +\
1.4 * (blk.P * blk.Gpc[t]**2 * 0.5 /
blk.cold_side.properties_in[t].dens_mass) + \
blk.cold_side.properties_in[t].dens_mass * \
9.81 * (blk.plate_length + blk.port_dia)
self.dP_c = Expression(self.flowsheet().config.time,
rule=rule_coldside_dP,
doc='Coldside pressure drop [Pa]')
def rule_eq_deltaP_hot(blk, t):
return blk.hot_side.deltaP[t] == -blk.dP_h[t]
self.eq_deltaP_hot = Constraint(self.flowsheet().config.time,
rule=rule_eq_deltaP_hot)
def rule_eq_deltaP_cold(blk, t):
return blk.cold_side.deltaP[t] == -blk.dP_c[t]
self.eq_deltaP_cold = Constraint(self.flowsheet().config.time,
rule=rule_eq_deltaP_cold)
# ----------------------------------------------------------------------
# Overall heat transfer coefficients
def rule_U(blk, t, p):
return 1.0 /\
(1.0 / blk.h_hot[t, p] + blk.plate_gap / blk.plate_thermal_cond +
1.0 / blk.h_cold[t, p])
self.U = Expression(self.flowsheet().config.time, self.PH,
rule=rule_U,
doc='Overall heat transfer coefficient')
# ----------------------------------------------------------------------
# capacitance of hot and cold fluid
def rule_Caph(blk, t, p):
return blk.mh_in[t] * blk.cp_hot[t] / blk.Np[p]
self.Caph = Expression(self.flowsheet().config.time, self.PH,
rule=rule_Caph,
doc='Hotfluid capacitance rate')
def rule_Capc(blk, t, p):
return blk.mc_in[t] * blk.cp_cold[t] / blk.Np[p]
self.Capc = Expression(self.flowsheet().config.time, self.PH,
rule=rule_Capc,
doc='Coldfluid capacitance rate')
# ----------------------------------------------------------------------
# min n max capacitance and capacitance ratio
def rule_Cmin(blk, t, p):
return 0.5 * (blk.Caph[t, p] + blk.Capc[t, p] -
((blk.Caph[t, p] - blk.Capc[t, p])**2 + 0.00001)**0.5)
self.Cmin = Expression(self.flowsheet().config.time, self.PH,
rule=rule_Cmin,
doc='Minimum capacitance rate')
def rule_Cmax(blk, t, p):
return 0.5 * (blk.Caph[t, p] + blk.Capc[t, p] +
((blk.Caph[t, p] - blk.Capc[t, p])**2 + 0.00001)**0.5)
self.Cmax = Expression(self.flowsheet().config.time, self.PH,
rule=rule_Cmax,
doc='Maximum capacitance rate')
def rule_CR(blk, t, p):
return blk.Cmin[t, p] / blk.Cmax[t, p]
self.CR = Expression(self.flowsheet().config.time, self.PH,
rule=rule_CR,
doc='Capacitance ratio')
# ----------------------------------------------------------------------
# Number of Transfer units for sub heat exchanger
def rule_NTU(blk, t, p):
return blk.U[t, p] * blk.plate_area / blk.Cmin[t, p]
self.NTU = Expression(self.flowsheet().config.time, self.PH,
rule=rule_NTU,
doc='Number of Transfer Units')
# ----------------------------------------------------------------------
# effectiveness of sub-heat exchangers
def rule_Ecf(blk, t, p):
if blk.P.value % 2 == 0:
return (1 - exp(-blk.NTU[t, p] * (1 - blk.CR[t, p]))) / \
(1 - blk.CR[t, p] *
exp(-blk.NTU[t, p] * (1 - blk.CR[t, p])))
elif blk.P.value % 2 == 1:
return (1 - exp(-blk.NTU[t, p] * (1 + blk.CR[t, p]))) / (1 + blk.CR[t, p])
self.Ecf = Expression(self.flowsheet().config.time, self.PH,
rule=rule_Ecf,
doc='Effectiveness for sub-HX')
# ----------------------------------------------------------------------
# Energy balance equations for hot fluid in sub-heat exhanger
def rule_Ebh_eq(blk, t, p):
return blk.Th_out[t, p] == blk.Th_in[t, p] -\
blk.Ecf[t, p] * blk.Cmin[t, p] / blk.Caph[t, p] * \
(blk.Th_in[t, p] - blk.Tc_in[t, p])
self.Ebh_eq = Constraint(self.flowsheet().config.time, self.PH,
rule=rule_Ebh_eq,
doc='Hot fluid sub-heat exchanger energy balance')
# Hot fluid exit temperature
def rule_Tout_hot(blk, t):
return blk.Th_out[t, blk.P.value] ==\
blk.hot_side.properties_out[t].temperature
self.Tout_hot_eq = Constraint(self.flowsheet().config.time,
rule=rule_Tout_hot,
doc='Hot fluid exit temperature')
# Energy balance equations for cold fluid in sub-heat exhanger
def rule_Ebc_eq(blk, t, p):
return blk.Tc_out[t, p] == blk.Tc_in[t, p] + \
blk.Ecf[t, p] * blk.Cmin[t, p] / blk.Capc[t, p] * \
(blk.Th_in[t, p] - blk.Tc_in[t, p])
self.Ebc_eq = Constraint(self.flowsheet().config.time, self.PH,
rule=rule_Ebc_eq,
doc='Cold fluid sub-heat exchanger energy balance')
# Cold fluid exit temperature
def rule_Tout_cold(blk, t):
return blk.Tc_out[t, 1] ==\
blk.cold_side.properties_out[t].temperature
self.Tout_cold_eq = Constraint(self.flowsheet().config.time,
rule=rule_Tout_cold,
doc='Cold fluid exit temperature')
# ----------------------------------------------------------------------
# Energy balance boundary conditions
def rule_hot_BCIN(blk, t):
return blk.Th_in[t, 1] == \
blk.hot_side.properties_in[t].temperature
self.hot_BCIN = Constraint(self.flowsheet().config.time,
rule=rule_hot_BCIN,
doc='Hot fluid inlet boundary conditions')
def rule_cold_BCIN(blk, t):
return blk.Tc_in[t, blk.P.value] ==\
blk.cold_side.properties_in[t].temperature
self.cold_BCIN = Constraint(self.flowsheet().config.time,
rule=rule_cold_BCIN,
doc='Cold fluid inlet boundary conditions')
Pset = [i for i in range(1, self.P.value)]
def rule_hot_BC(blk, t, p):
return blk.Th_out[t, p] == blk.Th_in[t, p + 1]
self.hot_BC = Constraint(self.flowsheet().config.time, Pset,
rule=rule_hot_BC,
doc='Hot fluid boundary conditions: change of pass')
def rule_cold_BC(blk, t, p):
return blk.Tc_out[t, p + 1] == blk.Tc_in[t, p]
self.cold_BC = Constraint(self.flowsheet().config.time, Pset,
rule=rule_cold_BC,
doc='Cold fluid boundary conditions: change of pass')
# ----------------------------------------------------------------------
# Energy transferred
def rule_QH(blk, t):
return blk.mh_in[t] * blk.cp_hot[t] *\
(blk.hot_side.properties_in[t].temperature -
blk.hot_side.properties_out[t].temperature)
self.QH = Expression(self.flowsheet().config.time, rule=rule_QH,
doc='Heat lost by hot fluid')
def rule_QC(blk, t):
return blk.mc_in[t] * blk.cp_cold[t] *\
(blk.cold_side.properties_out[t].temperature -
blk.cold_side.properties_in[t].temperature)
self.QC = Expression(self.flowsheet().config.time, rule=rule_QH,
doc='Heat gain by cold fluid')
[docs] def initialize(blk, hotside_state_args=None, coldside_state_args=None,
outlvl=idaeslog.NOTSET, solver=None, optarg=None):
'''
Initialisation routine for PHE unit (default solver ipopt)
Keyword Arguments:
state_args : a dict of arguments to be passed to the property
package(s) to provide an initial state for
initialization (see documentation of the specific
property package) (default = {}).
outlvl : sets output level of initialization routine
optarg : solver options dictionary object (default=None, use
default solver options)
solver : str indicating which solver to use during
initialization (default = None)
Returns:
None
'''
# Set solver options
init_log = idaeslog.getInitLogger(blk.name, outlvl, tag='unit')
solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit")
# Create solver
opt = get_solver(solver, optarg)
hotside_state_args = {
'flow_mol': value(blk.hot_inlet.flow_mol[0]),
'temperature': value(blk.hot_inlet.temperature[0]),
'pressure': value(blk.hot_inlet.pressure[0]),
'mole_frac_comp':
{'H2O': value(blk.hot_inlet.mole_frac_comp[0, 'H2O']),
'CO2': value(blk.hot_inlet.mole_frac_comp[0, 'CO2']),
'MEA': value(blk.hot_inlet.mole_frac_comp[0, 'MEA'])}}
coldside_state_args = {
'flow_mol': value(blk.cold_inlet.flow_mol[0]),
'temperature': value(blk.cold_inlet.temperature[0]),
'pressure': value(blk.cold_inlet.pressure[0]),
'mole_frac_comp':
{'H2O': value(blk.cold_inlet.mole_frac_comp[0, 'H2O']),
'CO2': value(blk.cold_inlet.mole_frac_comp[0, 'CO2']),
'MEA': value(blk.cold_inlet.mole_frac_comp[0, 'MEA'])}}
# ---------------------------------------------------------------------
# Initialize the INLET properties
init_log.info('STEP 1: PROPERTY INITIALIZATION')
init_log.info_high("INLET Properties initialization")
blk.hot_side.properties_in.initialize(
state_args=hotside_state_args,
outlvl=outlvl,
optarg=optarg,
solver=solver,
hold_state=True)
blk.cold_side.properties_in.initialize(
state_args=coldside_state_args,
outlvl=outlvl,
optarg=optarg,
solver=solver,
hold_state=True)
# Initialize the OUTLET properties
init_log.info_high("OUTLET Properties initialization")
blk.hot_side.properties_out.initialize(
state_args=hotside_state_args,
outlvl=outlvl,
optarg=optarg,
solver=solver,
hold_state=False)
blk.cold_side.properties_out.initialize(
state_args=coldside_state_args,
outlvl=outlvl,
optarg=optarg,
solver=solver,
hold_state=False)
# ----------------------------------------------------------------------
init_log.info('STEP 2: PHE INITIALIZATION')
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
res = opt.solve(blk, tee=slc.tee)
init_log.info_high("STEP 2 Complete: {}.".format(idaeslog.condition(res)))
init_log.info('INITIALIZATION COMPLETED')