Source code for idaes.models.heat_exchanger

##############################################################################
# Institute for the Design of Advanced Energy Systems Process Systems
# Engineering Framework (IDAES PSE Framework) Copyright (c) 2018, 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".
##############################################################################
"""
Standard IDAES heat exchanger model.
"""
from __future__ import division

# Import Python libraries
import logging

# Import Pyomo libraries
from pyomo.environ import log, SolverFactory, value, Var
from pyomo.opt import TerminationCondition
from pyomo.common.config import ConfigValue, In

# Import IDAES cores
from idaes.core import UnitBlockData, declare_process_block_class, \
                        Holdup0D, CONFIG_Base
from idaes.core.util.config import is_parameter_block, list_of_strings
from idaes.core.util.misc import add_object_ref

__author__ = "Andrew Lee"


# Add support for 1D Heat Exchanger model

# Set up logger
logger = logging.getLogger('idaes.unit_model')


[docs]@declare_process_block_class("HeatExchanger") class HeatExchangerData(UnitBlockData): """ Standard Heat Exchanger Unit Model Class """ CONFIG = CONFIG_Base() # Set default values of inherited attributes CONFIG.get("has_phase_equilibrium")._default = True CONFIG.get("has_heat_transfer")._default = True # Add unit model attributes CONFIG.declare("side_1_property_package", ConfigValue( default=None, domain=is_parameter_block, description="Property package to use for side_1 holdup", doc="""Property parameter object used to define property calculations (default = 'use_parent_value') - 'use_parent_value' - get package from parent (default = None) - a ParameterBlock object""")) CONFIG.declare("side_1_property_package_args", ConfigValue( default={}, description="Arguments for constructing side_1 property package", doc="""A dict of arguments to be passed to a property block and used when constructing these (default = 'use_parent_value') - 'use_parent_value' - get package from parent (default = None) - a dict (see property package for documentation)""")) CONFIG.declare("side_2_property_package", ConfigValue( default=None, domain=is_parameter_block, description="Property package to use for side_2 holdup", doc="""Property parameter object used to define property calculations (default = 'use_parent_value') - 'use_parent_value' - get package from parent (default = None) - a ParameterBlock object""")) CONFIG.declare("side_2_property_package_args", ConfigValue( default={}, description="Arguments for constructing side_2 property package", doc="""A dict of arguments to be passed to a property block and used when constructing these (default = 'use_parent_value') - 'use_parent_value' - get package from parent (default = None) - a dict (see property package for documentation)""")) CONFIG.declare("side_1_inlet_list", ConfigValue( domain=list_of_strings, description="List of inlet names for side_1 of heat exchanger", doc="""A list containing names of inlets for side_1 of heat exchanger (default = None) - None - default single inlet - list - a list of names for inlets""")) CONFIG.declare("side_1_num_inlets", ConfigValue( domain=int, description="Number of inlets to side_1 of heat exchanger", doc="""Argument indication number (int) of inlets to construct to side_1 of heat exchanger (default = None). Not used if side_1_inlet_list arg is provided. - None - use side_1_inlet_list arg instead - int - Inlets will be named with sequential numbers from 1 to num_inlets""")) CONFIG.declare("side_1_outlet_list", ConfigValue( domain=list_of_strings, description="List of outlet names for side_1 of heat exchanger", doc="""A list containing names of outlets for side_1 of heat exchanger (default = None) - None - default single outlet - list - a list of names for outlets""")) CONFIG.declare("side_1_num_outlets", ConfigValue( domain=int, description="Number of outlets to side_1 of heat exchanger", doc="""Argument indication number (int) of outlets to construct to side_1 of heat exchanger (default = None). Not used if side_1_outlet_list arg is provided. - None - use side_1_outlet_list arg instead - int - Outlets will be named with sequential numbers from 1 to num_outlets""")) CONFIG.declare("side_2_inlet_list", ConfigValue( domain=list_of_strings, description="List of inlet names for side_2 of heat exchanger", doc="""A list containing names of inlets for side_2 of heat exchanger (default = None) - None - default single inlet - list - a list of names for inlets""")) CONFIG.declare("side_2_num_inlets", ConfigValue( domain=int, description="Number of inlets to side_2 of heat exchanger", doc="""Argument indication number (int) of inlets to construct to side_2 of heat exchanger (default = None). Not used if side_2_inlet_list arg is provided. - None - use side_2_inlet_list arg instead - int - Inlets will be named with sequential numbers from 1 to num_inlets""")) CONFIG.declare("side_2_outlet_list", ConfigValue( domain=list_of_strings, description="List of outlet names for side_2 of heat exchanger", doc="""A list containing names of outlets for side_2 of heat exchanger (default = None) - None - default single outlet - list - a list of names for outlets""")) CONFIG.declare("side_2_num_outlets", ConfigValue( domain=int, description="Number of outlets to side_2 of heat exchanger", doc="""Argument indication number (int) of outlets to construct to side_2 of heat exchanger (default = None). Not used if side_2_outlet_list arg is provided. - None - use side_2_outlet_list arg instead - int - Outlets will be named with sequential numbers from 2 to num_outlets""")) CONFIG.declare("flow_type", ConfigValue( default='counter-current', domain=In(['counter-current']), description="Flow configuration in unit", doc="""Flag indicating type of flow arrangement to use for heat exchanger (default = 'counter-current') - 'counter-current' : counter-current flow arrangement"""))
[docs] def build(self): """ Begin building model (pre-DAE transformation) Args: None Returns: None """ # Call UnitModel.build to setup dynamics super(HeatExchangerData, self).build() # Build Holdup Block self.side_1 = Holdup0D( property_package=self.config.side_1_property_package, property_package_args=self.config.side_1_property_package_args) self.side_2 = Holdup0D( property_package=self.config.side_2_property_package, property_package_args=self.config.side_2_property_package_args) # Set Unit Geometry and Holdup Volume self._set_geometry() # Construct performance equations self._make_performance() # Construct performance equations if self.config.flow_type == "counter-current": self._make_counter_current()
[docs] def post_transform_build(self): """ Continue model construction after DAE transformation Args: None Returns: None """ # Construct Inlets self.build_inlets(holdup='side_1', inlets=self.config.side_1_inlet_list, num_inlets=self.config.side_1_num_inlets) self.build_inlets(holdup='side_2', inlets=self.config.side_2_inlet_list, num_inlets=self.config.side_2_num_inlets) # Build Outlets self.build_outlets(holdup='side_1', outlets=self.config.side_1_outlet_list, num_outlets=self.config.side_1_num_outlets) self.build_outlets(holdup='side_2', outlets=self.config.side_2_outlet_list, num_outlets=self.config.side_2_num_outlets)
def _set_geometry(self): """ Define the geometry of the unit as necessary, and link to holdup volume Args: None Returns: None """ # For this case, just create a reference to holdup volumes if self.config.include_holdup is True: add_object_ref(self, "volume_side_1", self.side_1.volume) add_object_ref(self, "volume_side_2", self.side_2.volume) def _make_performance(self): """ Define constraints which describe the behaviour of the unit model. Args: None Returns: None """ # Set references to balance terms at unit level add_object_ref(self, "heat", self.side_1.heat) # Performance variables self.heat_transfer_area = Var(initialize=1.0, doc='Surface area of heat exchanger') self.temperature_driving_force = Var(self.time, initialize=1.0, doc='Mean driving force for ' 'heat exchange') self.side_1_inlet_dT = Var(self.time, initialize=1.0, doc='Temperature difference at side 1 feed') self.side_1_outlet_dT = Var(self.time, initialize=1.0, doc='Temperature difference at side 1 ' 'discharge') self.heat_transfer_coefficient = Var(initialize=1.0, doc='Heat transfer coefficient') # Energy balance equation @self.Constraint(self.time, doc="Energy balance between two sides") def energy_balance(b, t): return b.side_1.heat[t] == -b.side_2.heat[t] # Heat transfer correlation @self.Constraint(self.time, doc="Heat transfer correlation") def heat_transfer_correlation(b, t): return b.heat[t] == -(b.heat_transfer_coefficient * b.heat_transfer_area * b.temperature_driving_force[t]) # Driving Force @self.Constraint(self.time, doc="Log mean temperature difference calculation") def LMTD(b, t): return (b.side_1_inlet_dT[t] - b.side_1_outlet_dT[t]) == ( b.temperature_driving_force[t] * (log(b.side_1_inlet_dT[t]/b.side_1_outlet_dT[t]))) def _make_counter_current(self): """ Add temperature driving force Constraints for counter-current flow. Args: None Returns: None """ # Temperature Differences @self.Constraint(self.time, doc="Side 1 inlet temperature difference") def temperature_difference_1(b, t): return b.side_1_inlet_dT[t] == ( b.side_1.properties_in[t].temperature - b.side_2.properties_out[t].temperature) @self.Constraint(self.time, doc="Side 1 outlet temperature difference") def temperature_difference_2(b, t): return b.side_1_outlet_dT[t] == ( b.side_1.properties_out[t].temperature - b.side_2.properties_in[t].temperature)
[docs] def model_check(blk): """ Model checks for unit - calls model checks for both Holdup Blocks. Args: None Returns: None """ # Run holdup block model checks blk.side_1.model_check() blk.side_2.model_check()
[docs] def initialize(blk, state_args_1={}, state_args_2={}, outlvl=0, solver='ipopt', optarg={'tol': 1e-6}): ''' General Heat Exchanger initialisation routine. Keyword Arguments: state_args_1 : a dict of arguments to be passed to the property package(s) for side 1 of the heat exchanger to provide an initial state for initialization (see documentation of the specific property package) (default = {}). state_args_2 : a dict of arguments to be passed to the property package(s) for side 2 of the heat exchanger to provide an initial state for initialization (see documentation of the specific property package) (default = {}). outlvl : sets output level of initialisation routine * 0 = no output (default) * 1 = return solver state for each step in routine * 2 = return solver state for each step in subroutines * 3 = include solver output infomation (tee=True) optarg : solver options dictionary object (default={'tol': 1e-6}) solver : str indicating whcih solver to use during initialization (default = 'ipopt') Returns: None ''' # Set solver options if outlvl > 3: stee = True else: stee = False opt = SolverFactory(solver) opt.options = optarg # --------------------------------------------------------------------- # Initialize inlet property blocks flags1 = blk.side_1.initialize(outlvl=outlvl-1, optarg=optarg, solver=solver, state_args=state_args_1) flags2 = blk.side_2.initialize(outlvl=outlvl-1, optarg=optarg, solver=solver, state_args=state_args_2) if outlvl > 0: logger.info('{} Initialisation Step 1 Complete.'.format(blk.name)) # --------------------------------------------------------------------- # Initialize temperature differentials # Fix variables for t in blk.time: T = (value(blk.side_1.properties_in[t].temperature) - value(blk.side_2.properties_in[t].temperature))*0.1 blk.side_1.properties_out[t].temperature.fix( value(blk.side_1.properties_in[t].temperature)-T) blk.side_2.properties_out[t].temperature.fix( value(blk.side_2.properties_in[t].temperature)+T) # Deactivate Constraints blk.energy_balance.deactivate() blk.LMTD.deactivate() blk.heat_transfer_correlation.deactivate() results = opt.solve(blk, tee=stee) if outlvl > 0: if results.solver.termination_condition == \ TerminationCondition.optimal: logger.info('{} Initialisation Step 2 Complete.' .format(blk.name)) else: logger.warning('{} Initialisation Step 2 Failed.' .format(blk.name)) # --------------------------------------------------------------------- # Estimate cross-over temperature and solve for t in blk.time: dh1 = (value(blk.side_1.heat[t]) / (value(blk.side_1.properties_in[t].temperature) - value(blk.side_1.properties_out[t].temperature))) dh2 = (value(blk.side_2.heat[t]) / (value(blk.side_2.properties_in[t].temperature) - value(blk.side_2.properties_out[t].temperature))) Tx = (dh1*value(blk.side_1.properties_in[t].temperature) + dh2*value(blk.side_2.properties_in[t].temperature))/(dh1+dh2) blk.side_1.properties_out[t].temperature.fix(Tx) blk.side_2.properties_out[t].temperature.fix(Tx) results = opt.solve(blk, tee=stee) if outlvl > 0: if results.solver.termination_condition == \ TerminationCondition.optimal: logger.info('{} Initialisation Step 3 Complete.' .format(blk.name)) else: logger.warning('{} Initialisation Step 3 Failed.' .format(blk.name)) # --------------------------------------------------------------------- # Activate energy balance and driving force for t in blk.time: blk.side_1_inlet_dT[t] = ( value(blk.side_1.properties_in[t].temperature) - value(blk.side_2.properties_out[t].temperature)) blk.side_1_outlet_dT[t] = ( value(blk.side_1.properties_out[t].temperature) - value(blk.side_2.properties_in[t].temperature)) blk.temperature_driving_force[t] = ( (value(blk.side_1_inlet_dT[t]) - value(blk.side_1_outlet_dT[t])) / log(value(blk.side_1_inlet_dT[t]) / value(blk.side_1_outlet_dT[t]))) blk.heat[t] = -(value(blk.heat_transfer_coefficient) * value(blk.heat_transfer_area) * value(blk.temperature_driving_force[t])) blk.side_2.properties_out[t].temperature.unfix() blk.side_1.properties_out[t].temperature.unfix() blk.heat_transfer_correlation.activate() blk.LMTD.activate() blk.energy_balance.activate() results = opt.solve(blk, tee=stee) if outlvl > 0: if results.solver.termination_condition == \ TerminationCondition.optimal: logger.info('{} Initialisation Step 4 Complete.' .format(blk.name)) else: logger.warning('{} Initialisation Step 4 Failed.' .format(blk.name)) # --------------------------------------------------------------------- # Release Inlet state blk.side_1.release_state(flags1, outlvl-1) blk.side_2.release_state(flags2, outlvl-1) if outlvl > 0: logger.info('{} Initialisation Complete.'.format(blk.name))