Source code for idaes.models_extra.gas_solid_contactors.unit_models.moving_bed

#################################################################################
# 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-2024 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.
#################################################################################
"""
IDAES Moving Bed Model.

The moving bed model is a 1D axially discretized model with a gas and
solid phase and a counter-current flow direction. The model captures
the gas-solid interaction between both phases through reaction, mass
and heat transfer.

Equations written in this model were derived from:
(1) A. Ostace, A. Lee, C.O. Okoli, A.P. Burgard, D.C. Miller, D. Bhattacharyya,
Mathematical modeling of a moving-bed reactor for chemical looping combustion
of methane, in: M.R. Eden, M. Ierapetritou, G.P. Towler (Eds.),13th Int. Symp.
Process Syst. Eng. (PSE 2018), Computer-Aided Chemical Engineering 2018,
pp. 325–330 , San Diego, CA.
(2) C.O. Okoli, A. Ostace, S. Nadgouda, A. Lee, A. Tong, A.P. Burgard,
D. Bhattacharyya, D.C. Miller, 2020.
A framework for the optimization of chemical looping combustion processes,
Journal of Powder Technology, Vol. 365, pp 149 - 162.

Assumptions:
Property package contains temperature and pressure variables.
Property package contains minimum fluidization velocity.

"""

# Import Python libraries
import matplotlib.pyplot as plt

# Import Pyomo libraries
from pyomo.environ import (
    Var,
    Param,
    Reals,
    value,
    TransformationFactory,
    Constraint,
    check_optimal_termination,
    units as pyunits,
)
from pyomo.common.config import ConfigBlock, ConfigValue, In, Bool
from pyomo.util.calc_var_value import calculate_variable_from_constraint
from pyomo.dae import ContinuousSet

# Import IDAES cores
from idaes.core import (
    ControlVolume1DBlock,
    UnitModelBlockData,
    declare_process_block_class,
    MaterialBalanceType,
    EnergyBalanceType,
    MomentumBalanceType,
    FlowDirection,
    DistributedVars,
)
from idaes.core.util.config import (
    is_physical_parameter_block,
    is_reaction_parameter_block,
)
from idaes.core.util.exceptions import (
    ConfigurationError,
    BurntToast,
    InitializationError,
)
from idaes.core.util.tables import create_stream_table_dataframe
from idaes.core.util.constants import Constants as constants
from idaes.core.util.math import smooth_abs
from idaes.core.util.misc import add_object_reference
import idaes.logger as idaeslog
from idaes.core.util import scaling as iscale
from idaes.core.solvers import get_solver

__author__ = "Chinedu Okoli", "Anca Ostace"

# Set up logger
_log = idaeslog.getLogger(__name__)


[docs] @declare_process_block_class("MBR") class MBRData(UnitModelBlockData): """Standard Moving Bed Unit Model Class.""" # Create template for unit level config arguments CONFIG = UnitModelBlockData.CONFIG() # Unit level config arguments CONFIG.declare( "finite_elements", ConfigValue( default=10, domain=int, description="Number of finite elements length domain", doc="""Number of finite elements to use when discretizing length domain (default=20)""", ), ) CONFIG.declare( "length_domain_set", ConfigValue( default=[0.0, 1.0], domain=list, description="Number of finite elements length domain", doc="""length_domain_set - (optional) list of point to use to initialize a new ContinuousSet if length_domain is not provided (default = [0.0, 1.0])""", ), ) CONFIG.declare( "transformation_method", ConfigValue( default="dae.finite_difference", description="Method to use for DAE transformation", doc="""Method to use to transform domain. Must be a method recognized by the Pyomo TransformationFactory, **default** - "dae.finite_difference". **Valid values:** { **"dae.finite_difference"** - Use a finite difference transformation method, **"dae.collocation"** - use a collocation transformation method}""", ), ) CONFIG.declare( "transformation_scheme", ConfigValue( default=None, domain=In([None, "BACKWARD", "FORWARD", "LAGRANGE-RADAU"]), description="Scheme to use for DAE transformation", doc="""Scheme to use when transforming domain. If specified, this scheme is applied to discretizations in both the gas and solid phases. In this case, ``gas_transformation_scheme`` and ``solid_transformation_scheme`` cannot be specified. See Pyomo documentation for supported schemes. **default** - None. **Valid values:** { **None** - defaults to "BACKWARD" for finite difference transformation method, and to "LAGRANGE-RADAU" for collocation transformation method, **"BACKWARD"** - Use a finite difference transformation method, **"FORWARD""** - use a finite difference transformation method, **"LAGRANGE-RADAU""** - use a collocation transformation method}""", ), ) CONFIG.declare( "gas_transformation_scheme", ConfigValue( default=None, domain=In([None, "BACKWARD", "FORWARD"]), description="Scheme to use for DAE transformation", doc="""Scheme to use when transforming length domain of the gas phase. If this option is supplied, ``solid_transformation_scheme`` must be supplied as well. This cannot be set (other than to ``None``) if the ``transformation_scheme`` option is set. See Pyomo documentation for supported schemes. **default** - None. **Valid values:** { **None** - defaults to the value of the ``transformation_scheme`` option, **"BACKWARD"** - Use a finite difference transformation method, **"FORWARD""** - use a finite difference transformation method}""", ), ) CONFIG.declare( "solid_transformation_scheme", ConfigValue( default=None, domain=In([None, "BACKWARD", "FORWARD"]), description="Scheme to use for DAE transformation", doc="""Scheme to use when transforming length domain of the solid phase. If this option is supplied, ``gas_transformation_scheme`` must be supplied as well. This cannot be set (other than to ``None``) if the ``transformation_scheme`` option is set. See Pyomo documentation for supported schemes, **default** - None. **Valid values:** { **None** - defaults to the value of the ``transformation_scheme`` option, **"BACKWARD"** - Use a finite difference transformation method, **"FORWARD""** - use a finite difference transformation method}""", ), ) CONFIG.declare( "collocation_points", ConfigValue( default=3, domain=int, description="Number of collocation points per finite element", doc="""Number of collocation points to use per finite element when discretizing length domain (default=3)""", ), ) CONFIG.declare( "flow_type", ConfigValue( default="counter_current", domain=In(["counter_current"]), description="Flow configuration of Moving Bed", doc="""Flow configuration of Moving Bed - counter_current: gas side flows from 0 to 1 solid side flows from 1 to 0""", ), ) CONFIG.declare( "material_balance_type", ConfigValue( default=MaterialBalanceType.componentTotal, domain=In(MaterialBalanceType), description="Material balance construction flag", doc="""Indicates what type of mass balance should be constructed, **default** - MaterialBalanceType.componentTotal. **Valid values:** { **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.}""", ), ) CONFIG.declare( "energy_balance_type", ConfigValue( default=EnergyBalanceType.enthalpyTotal, domain=In(EnergyBalanceType), description="Energy balance construction flag", doc="""Indicates what type of energy balance should be constructed, **default** - EnergyBalanceType.enthalpyTotal. **Valid values:** { **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.}""", ), ) CONFIG.declare( "momentum_balance_type", ConfigValue( default=MomentumBalanceType.pressureTotal, domain=In(MomentumBalanceType), description="Momentum balance construction flag", doc="""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.}""", ), ) CONFIG.declare( "has_pressure_change", ConfigValue( default=True, domain=Bool, description="Pressure change term construction flag", doc="""Indicates whether terms for pressure change should be constructed, **default** - False. **Valid values:** { **True** - include pressure change terms, **False** - exclude pressure change terms.}""", ), ) CONFIG.declare( "pressure_drop_type", ConfigValue( default="simple_correlation", domain=In(["simple_correlation", "ergun_correlation"]), description="Construction flag for type of pressure drop", doc="""Indicates what type of pressure drop correlation should be used, **default** - "simple_correlation". **Valid values:** { **"simple_correlation"** - Use a simplified pressure drop correlation, **"ergun_correlation"** - Use the Ergun equation.}""", ), ) # Create template for phase specific config arguments _PhaseTemplate = UnitModelBlockData.CONFIG() _PhaseTemplate.declare( "has_equilibrium_reactions", ConfigValue( default=False, domain=Bool, description="Equilibrium reaction construction flag", doc="""Indicates whether terms for equilibrium controlled reactions should be constructed, **default** - True. **Valid values:** { **True** - include equilibrium reaction terms, **False** - exclude equilibrium reaction terms.}""", ), ) _PhaseTemplate.declare( "property_package", ConfigValue( default=None, domain=is_physical_parameter_block, description="Property package to use for control volume", 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""", ), ) _PhaseTemplate.declare( "property_package_args", ConfigValue( default={}, domain=dict, description="Arguments for constructing gas property package", doc="""A dict of arguments to be passed to the PropertyBlockData 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)""", ), ) _PhaseTemplate.declare( "reaction_package", ConfigValue( default=None, domain=is_reaction_parameter_block, description="Reaction package to use for control volume", doc="""Reaction parameter object used to define reaction calculations, **default** - None. **Valid values:** { **None** - no reaction package, **ReactionParameterBlock** - a ReactionParameterBlock object.}""", ), ) _PhaseTemplate.declare( "reaction_package_args", ConfigBlock( implicit=True, implicit_domain=ConfigBlock, description="Arguments to use for constructing reaction packages", doc="""A ConfigBlock with arguments to be passed to a reaction block(s) and used when constructing these, **default** - None. **Valid values:** { see reaction package for documentation.}""", ), ) # Create individual config blocks for gas and solid sides CONFIG.declare("gas_phase_config", _PhaseTemplate(doc="gas phase config arguments")) CONFIG.declare( "solid_phase_config", _PhaseTemplate(doc="solid phase config arguments") ) # =========================================================================
[docs] def build(self): """ Begin building model (pre-DAE transformation). Args: None Returns: None """ # Call UnitModel.build to build default attributes super().build() # Consistency check for transformation method and transformation scheme if ( self.config.transformation_method == "dae.finite_difference" and self.config.transformation_scheme is None ): self.config.transformation_scheme = "BACKWARD" elif ( self.config.transformation_method == "dae.collocation" and self.config.transformation_scheme is None ): self.config.transformation_scheme = "LAGRANGE-RADAU" elif ( self.config.transformation_method == "dae.finite_difference" and self.config.transformation_scheme != "BACKWARD" and self.config.transformation_scheme != "FORWARD" ): raise ConfigurationError( "{} invalid value for " "transformation_scheme argument. " "Must be " "BACKWARD" " or " "FORWARD" " " "if transformation_method is" " " "dae.finite_difference" ".".format(self.name) ) elif ( self.config.transformation_method == "dae.collocation" and self.config.transformation_scheme != "LAGRANGE-RADAU" ): raise ConfigurationError( "{} invalid value for " "transformation_scheme argument." "Must be " "LAGRANGE-RADAU" " if " "transformation_method is" " " "dae.collocation" ".".format(self.name) ) elif self.config.transformation_method != "dae.finite_difference" and ( self.config.gas_transformation_scheme is not None or self.config.solid_transformation_scheme is not None ): raise ConfigurationError( "Invalid configuration of {}. transformation_method must be" ' "dae.finite_difference" if gas_transformation_scheme or' " solid_transformation_scheme are set".format(self.name) ) elif (self.config.gas_transformation_scheme is None) != ( self.config.solid_transformation_scheme is None ): raise ConfigurationError( "Invalid configuration of {}. Either both" " gas_transformation_scheme and solid_transformation_scheme" " must be set, or neither must be set. Got {} and {}.".format( self.name, self.config.gas_transformation_scheme, self.config.solid_transformation_scheme, ) ) elif (self.config.transformation_scheme is not None) and ( (self.config.gas_transformation_scheme is not None) or (self.config.solid_transformation_scheme is not None) ): raise ConfigurationError( "Invalid configuration of {}. transformation_scheme cannot be" " specified if gas_transformation_scheme and" " solid_transformation_scheme are specified.".format(self.name) ) # Set flow directions for the control volume blocks # Gas flows from 0 to 1, solid flows from 1 to 0 # An if statement is used here despite only one option to allow for # future extensions to other flow configurations if self.config.flow_type == "counter_current": set_direction_gas = FlowDirection.forward set_direction_solid = FlowDirection.backward else: raise BurntToast( "{} encountered unrecognized argument " "for flow type. Please contact the IDAES" " developers with this bug.".format(self.name) ) # Set arguments for gas sides if homogeneous reaction block if self.config.gas_phase_config.reaction_package is not None: has_rate_reaction_gas_phase = True else: has_rate_reaction_gas_phase = False # Set arguments for gas and solid sides if heterogeneous reaction block if self.config.solid_phase_config.reaction_package is not None: has_rate_reaction_solid_phase = True has_mass_transfer_gas_phase = True else: has_rate_reaction_solid_phase = False has_mass_transfer_gas_phase = False # Set heat transfer terms if self.config.energy_balance_type != EnergyBalanceType.none: has_heat_transfer = True else: has_heat_transfer = False # Set heat of reaction terms if ( self.config.energy_balance_type != EnergyBalanceType.none and self.config.gas_phase_config.reaction_package is not None ): has_heat_of_reaction_gas_phase = True else: has_heat_of_reaction_gas_phase = False if ( self.config.energy_balance_type != EnergyBalanceType.none and self.config.solid_phase_config.reaction_package is not None ): has_heat_of_reaction_solid_phase = True else: has_heat_of_reaction_solid_phase = False # local aliases used to shorten object names solid_phase = self.config.solid_phase_config # Get units meta data from property packages units_meta_solid = solid_phase.property_package.get_metadata().get_derived_units # In this branch, we assume that either both gas and solid # transformation schemes are specified, or neither are specified. if ( # solid and gas transformation schemes are specified (self.config.solid_transformation_scheme is not None) and (self.config.gas_transformation_scheme is not None) ): # Create length domain sets for gas and solid phases. This is # necessary so we can apply different discretizations to the # different phases. self._gas_tr_scheme = self.config.gas_transformation_scheme self._solid_tr_scheme = self.config.solid_transformation_scheme self.solid_length_domain = ContinuousSet( bounds=(0.0, 1.0), initialize=self.config.length_domain_set, doc="Solid phase normalized length domain", ) self.gas_length_domain = ContinuousSet( bounds=(0.0, 1.0), initialize=self.config.length_domain_set, doc="Gas phase normalized length domain", ) # Create an instance attribute "length_domain" for convenience. # Arbitrarily, use the solid phase length domain. # # We do not use Reference as it does not work for Sets. add_object_reference(self, "length_domain", self.solid_length_domain) else: # Neither gas nor solid transformation schemes are specified self._gas_tr_scheme = self.config.transformation_scheme self._solid_tr_scheme = self.config.transformation_scheme self.length_domain = ContinuousSet( bounds=(0.0, 1.0), initialize=self.config.length_domain_set, doc="Moving bed normalized length domain", ) # Create instance attributes for gas and solid length domains add_object_reference(self, "solid_length_domain", self.length_domain) add_object_reference(self, "gas_length_domain", self.length_domain) self.bed_height = Var( domain=Reals, initialize=1, doc="Bed length", units=units_meta_solid("length"), ) # ========================================================================= # Build Control volume 1D for gas phase and populate gas control volume self.gas_phase = ControlVolume1DBlock( transformation_method=self.config.transformation_method, transformation_scheme=self._gas_tr_scheme, finite_elements=self.config.finite_elements, collocation_points=self.config.collocation_points, dynamic=self.config.dynamic, has_holdup=self.config.has_holdup, area_definition=DistributedVars.variant, property_package=self.config.gas_phase_config.property_package, property_package_args=self.config.gas_phase_config.property_package_args, reaction_package=self.config.gas_phase_config.reaction_package, reaction_package_args=self.config.gas_phase_config.reaction_package_args, ) self.gas_phase.add_geometry( length_domain=self.gas_length_domain, length_domain_set=self.config.length_domain_set, length_var=self.bed_height, flow_direction=set_direction_gas, ) self.gas_phase.add_state_blocks( information_flow=set_direction_gas, has_phase_equilibrium=False ) if self.config.gas_phase_config.reaction_package is not None: self.gas_phase.add_reaction_blocks( has_equilibrium=self.config.gas_phase_config.has_equilibrium_reactions ) self.gas_phase.add_material_balances( balance_type=self.config.material_balance_type, has_phase_equilibrium=False, has_mass_transfer=has_mass_transfer_gas_phase, has_rate_reactions=has_rate_reaction_gas_phase, ) self.gas_phase.add_energy_balances( balance_type=self.config.energy_balance_type, has_heat_transfer=has_heat_transfer, has_heat_of_reaction=has_heat_of_reaction_gas_phase, ) self.gas_phase.add_momentum_balances( balance_type=self.config.momentum_balance_type, has_pressure_change=self.config.has_pressure_change, ) # ========================================================================= # Build Control volume 1D for solid phase and populate solid control volume # Set argument for heterogeneous reaction block self.solid_phase = ControlVolume1DBlock( transformation_method=self.config.transformation_method, transformation_scheme=self._solid_tr_scheme, finite_elements=self.config.finite_elements, collocation_points=self.config.collocation_points, dynamic=self.config.dynamic, has_holdup=self.config.has_holdup, area_definition=DistributedVars.variant, property_package=self.config.solid_phase_config.property_package, property_package_args=self.config.solid_phase_config.property_package_args, reaction_package=self.config.solid_phase_config.reaction_package, reaction_package_args=self.config.solid_phase_config.reaction_package_args, ) self.solid_phase.add_geometry( length_domain=self.solid_length_domain, length_domain_set=self.config.length_domain_set, length_var=self.bed_height, flow_direction=set_direction_solid, ) self.solid_phase.add_state_blocks( information_flow=set_direction_solid, has_phase_equilibrium=False ) if self.config.solid_phase_config.reaction_package is not None: # TODO - a generalization of the heterogeneous reaction block # The heterogeneous reaction block does not use the # add_reaction_blocks in control volumes as control volumes are # currently setup to handle only homogeneous reaction properties. # Thus appending the heterogeneous reaction block to the # solid state block is currently hard coded here. tmp_dict = dict(**self.config.solid_phase_config.reaction_package_args) tmp_dict["gas_state_block"] = self.gas_phase.properties tmp_dict["solid_state_block"] = self.solid_phase.properties tmp_dict["has_equilibrium"] = ( self.config.solid_phase_config.has_equilibrium_reactions ) tmp_dict["parameters"] = self.config.solid_phase_config.reaction_package self.solid_phase.reactions = ( self.config.solid_phase_config.reaction_package.reaction_block_class( self.flowsheet().time, self.length_domain, doc="Reaction properties in control volume", **tmp_dict, ) ) self.solid_phase.add_material_balances( balance_type=self.config.material_balance_type, has_phase_equilibrium=False, has_mass_transfer=False, has_rate_reactions=has_rate_reaction_solid_phase, ) self.solid_phase.add_energy_balances( balance_type=self.config.energy_balance_type, has_heat_transfer=has_heat_transfer, has_heat_of_reaction=has_heat_of_reaction_solid_phase, ) self.solid_phase.add_momentum_balances( balance_type=MomentumBalanceType.none, has_pressure_change=False ) # ========================================================================= # Add ports # Add Ports for gas side self.add_inlet_port(name="gas_inlet", block=self.gas_phase) self.add_outlet_port(name="gas_outlet", block=self.gas_phase) # Add Ports for solid side self.add_inlet_port(name="solid_inlet", block=self.solid_phase) self.add_outlet_port(name="solid_outlet", block=self.solid_phase) # ========================================================================= # Add performance equation method self._apply_transformation() self._make_performance()
# ========================================================================= def _apply_transformation(self): """ Method to apply DAE transformation to the Control Volume length domain. Transformation applied will be based on the Control Volume configuration arguments. """ if self.config.finite_elements is None: raise ConfigurationError( "{} was not provided a value for the finite_elements" " configuration argument. Please provide a valid value.".format( self.name ) ) if self.config.transformation_method == "dae.finite_difference": self.discretizer = TransformationFactory(self.config.transformation_method) if (self.config.gas_transformation_scheme is not None) and ( self.config.solid_transformation_scheme is not None ): self.discretizer.apply_to( self, wrt=self.gas_length_domain, nfe=self.config.finite_elements, scheme=self._gas_tr_scheme, ) self.discretizer.apply_to( self, wrt=self.solid_length_domain, nfe=self.config.finite_elements, scheme=self._solid_tr_scheme, ) else: self.discretizer.apply_to( self, wrt=self.length_domain, nfe=self.config.finite_elements, scheme=self.config.transformation_scheme, ) elif self.config.transformation_method == "dae.collocation": self.discretizer = TransformationFactory(self.config.transformation_method) self.discretizer.apply_to( self, wrt=self.length_domain, nfe=self.config.finite_elements, ncp=self.config.collocation_points, scheme=self.config.transformation_scheme, ) def _make_performance(self): """ Constraints for unit model. Args: None Returns: None """ # local aliases used to shorten object names gas_phase = self.config.gas_phase_config solid_phase = self.config.solid_phase_config # Get units meta data from property packages units_meta_gas = gas_phase.property_package.get_metadata().get_derived_units units_meta_solid = solid_phase.property_package.get_metadata().get_derived_units # Declare Mutable Parameters self.eps = Param( mutable=True, default=1e-8, doc="Smoothing Factor for Smooth IF Statements", units=pyunits.dimensionless, ) # Unit Model variables self.bed_diameter = Var( initialize=1, doc="Reactor diameter", units=units_meta_solid("length") ) self.bed_area = Var( domain=Reals, initialize=1, doc="Reactor cross-sectional area", units=units_meta_solid("area"), ) # Phase specific variables self.velocity_superficial_gas = Var( self.flowsheet().time, self.length_domain, domain=Reals, initialize=0.05, doc="Gas superficial velocity", units=units_meta_gas("velocity"), ) self.velocity_superficial_solid = Var( self.flowsheet().time, domain=Reals, initialize=0.005, doc="Solid superficial velocity", units=units_meta_solid("velocity"), ) # Dimensionless numbers, mass and heat transfer coefficients self.Re_particle = Var( self.flowsheet().time, self.length_domain, domain=Reals, initialize=1.0, doc="Particle Reynolds number", units=pyunits.dimensionless, ) self.Pr_particle = Var( self.flowsheet().time, self.length_domain, domain=Reals, initialize=1.0, doc="Prandtl number of gas in bed", units=pyunits.dimensionless, ) self.Nu_particle = Var( self.flowsheet().time, self.length_domain, domain=Reals, initialize=1.0, doc="Particle Nusselt number", units=pyunits.dimensionless, ) self.gas_solid_htc = Var( self.flowsheet().time, self.length_domain, domain=Reals, initialize=1.0, doc="Gas-solid heat transfer coefficient", units=units_meta_gas("heat_transfer_coefficient"), ) # Fixed variables (these are parameters that can be estimated) self.bed_voidage = Var( domain=Reals, initialize=0.4, doc="Bed voidage", units=pyunits.dimensionless ) self.bed_voidage.fix() # ========================================================================= # Add performance equations # --------------------------------------------------------------------- # Geometry constraints # Bed area @self.Constraint(doc="Bed area") def bed_area_eqn(b): return b.bed_area == (constants.pi * (0.5 * b.bed_diameter) ** 2) # Area of gas side, and solid side @self.Constraint(self.flowsheet().time, self.length_domain, doc="Gas side area") def gas_phase_area(b, t, x): return ( b.gas_phase.area[t, x] == pyunits.convert(b.bed_area, to_units=units_meta_gas("area")) * b.bed_voidage ) @self.Constraint( self.flowsheet().time, self.length_domain, doc="Solid side area" ) def solid_phase_area(b, t, x): return b.solid_phase.area[t, x] == b.bed_area * (1 - b.bed_voidage) # --------------------------------------------------------------------- # Hydrodynamic constraints # Gas superficial velocity @self.Constraint( self.flowsheet().time, self.length_domain, doc="Gas superficial velocity" ) def gas_super_vel(b, t, x): return ( b.velocity_superficial_gas[t, x] * pyunits.convert(b.bed_area, to_units=units_meta_gas("area")) * b.gas_phase.properties[t, x].dens_mol == b.gas_phase.properties[t, x].flow_mol ) # Solid superficial velocity @self.Constraint( self.flowsheet().time, self.length_domain, doc="Solid superficial velocity" ) # This equation uses inlet values to compute the constant solid # superficial velocity, and then computes the solid particle density # through the rest of the bed. def solid_super_vel(b, t, x): return ( b.velocity_superficial_solid[t] * b.bed_area * b.solid_phase.properties[t, x].dens_mass_particle == b.solid_phase.properties[t, x].flow_mass ) # Gas side pressure drop calculation if ( self.config.has_pressure_change and self.config.pressure_drop_type == "simple_correlation" ): # Simplified pressure drop @self.Constraint( self.flowsheet().time, self.length_domain, doc="Gas side pressure drop calculation -" "simplified pressure drop", ) def gas_phase_config_pressure_drop(b, t, x): # 0.2/s is a unitted constant in the correlation return pyunits.convert( b.gas_phase.deltaP[t, x], to_units=units_meta_solid("pressure") / units_meta_solid("length"), ) == -(0.2 / pyunits.s) * ( b.velocity_superficial_gas[t, x] * ( pyunits.convert( b.solid_phase.properties[t, x].dens_mass_particle, to_units=units_meta_solid("density_mass"), ) - b.gas_phase.properties[t, x].dens_mass ) ) elif ( self.config.has_pressure_change and self.config.pressure_drop_type == "ergun_correlation" ): # Ergun equation @self.Constraint( self.flowsheet().time, self.length_domain, doc="Gas side pressure drop calculation -" "Ergun equation", ) def gas_phase_config_pressure_drop(b, t, x): return -pyunits.convert( b.gas_phase.deltaP[t, x], to_units=units_meta_solid("pressure") / units_meta_solid("length"), ) == ( (150 * pyunits.dimensionless) * (1 - b.bed_voidage) ** 2 * b.gas_phase.properties[t, x].visc_d * ( b.velocity_superficial_gas[t, x] + pyunits.convert( b.velocity_superficial_solid[t], to_units=units_meta_solid("velocity"), ) ) / ( pyunits.convert( b.solid_phase.properties[t, x].params.particle_dia, to_units=units_meta_solid("length"), ) ** 2 * b.bed_voidage**3 ) ) + ( (1.75 * pyunits.dimensionless) * b.gas_phase.properties[t, x].dens_mass * (1 - b.bed_voidage) * ( b.velocity_superficial_gas[t, x] + pyunits.convert( b.velocity_superficial_solid[t], to_units=units_meta_solid("velocity"), ) ) ** 2 / ( pyunits.convert( b.solid_phase.properties[t, x].params.particle_dia, to_units=units_meta_solid("length"), ) * b.bed_voidage**3 ) ) # to_units=deltaP_units) # The above expression has no absolute values - assumes: # (velocity_superficial_gas + velocity_superficial_solid) > 0 elif self.config.has_pressure_change is False: pass else: raise BurntToast( "{} encountered unrecognized argument for " "the pressure drop correlation. Please contact the IDAES" " developers with this bug.".format(self.name) ) # --------------------------------------------------------------------- # Reaction constraints # Build homogeneous reaction constraints if gas_phase.reaction_package is not None: # Gas side rate reaction extent @self.Constraint( self.flowsheet().time, self.length_domain, gas_phase.reaction_package.rate_reaction_idx, doc="Gas side rate reaction extent", ) def gas_phase_config_rxn_ext(b, t, x, r): return b.gas_phase.rate_reaction_extent[t, x, r] == ( b.gas_phase.reactions[t, x].reaction_rate[r] * b.gas_phase.area[t, x] ) # Build heterogeneous reaction constraints if solid_phase.reaction_package is not None: # Solid side rate reaction extent @self.Constraint( self.flowsheet().time, self.length_domain, solid_phase.reaction_package.rate_reaction_idx, doc="Solid side rate reaction extent", ) def solid_phase_config_rxn_ext(b, t, x, r): return b.solid_phase.rate_reaction_extent[t, x, r] == ( b.solid_phase.reactions[t, x].reaction_rate[r] * b.solid_phase.area[t, x] ) # Gas side heterogeneous rate reaction generation @self.Constraint( self.flowsheet().time, self.length_domain, gas_phase.property_package.phase_list, gas_phase.property_package.component_list, doc="Gas side heterogeneous" "rate reaction generation", ) def gas_comp_hetero_rxn(b, t, x, p, j): return b.gas_phase.mass_transfer_term[t, x, p, j] == ( sum( b.solid_phase.reactions[t, x].rate_reaction_stoichiometry[ r, p, j ] * b.solid_phase.reactions[t, x].reaction_rate[r] for r in (solid_phase.reaction_package.rate_reaction_idx) ) * b.solid_phase.area[t, x] ) # --------------------------------------------------------------------- if self.config.energy_balance_type != EnergyBalanceType.none: # Solid phase - gas to solid heat transfer @self.Constraint( self.flowsheet().time, self.length_domain, doc="Solid phase - gas to solid heat transfer", ) def solid_phase_heat_transfer(b, t, x): return pyunits.convert( b.solid_phase.heat[t, x], to_units=units_meta_gas("power") / units_meta_gas("length"), ) * pyunits.convert( b.solid_phase.properties[t, x].params.particle_dia, to_units=units_meta_gas("length"), ) == 6 * b.gas_solid_htc[ t, x ] * ( b.gas_phase.properties[t, x].temperature - pyunits.convert( b.solid_phase.properties[t, x].temperature, to_units=units_meta_gas("temperature"), ) ) * pyunits.convert( b.solid_phase.area[t, x], to_units=units_meta_gas("area") ) # Dimensionless numbers, mass and heat transfer coefficients # Particle Reynolds number @self.Constraint( self.flowsheet().time, self.length_domain, doc="Particle Reynolds number", ) def reynolds_number_particle(b, t, x): return ( b.Re_particle[t, x] * b.gas_phase.properties[t, x].visc_d == b.velocity_superficial_gas[t, x] * pyunits.convert( b.solid_phase.properties[t, x].params.particle_dia, to_units=units_meta_gas("length"), ) * b.gas_phase.properties[t, x].dens_mass ) # Prandtl number @self.Constraint( self.flowsheet().time, self.length_domain, doc="Prandtl number of gas in bed", ) def prandtl_number(b, t, x): return ( b.Pr_particle[t, x] * b.gas_phase.properties[t, x].therm_cond == b.gas_phase.properties[t, x].cp_mass * b.gas_phase.properties[t, x].visc_d ) # Particle Nusselt number @self.Constraint( self.flowsheet().time, self.length_domain, doc="Particle Nusselt number" ) def nusselt_number_particle(b, t, x): return ( b.Nu_particle[t, x] ** 3 == ( 2.0 + 1.1 * (smooth_abs(b.Re_particle[t, x], b.eps) ** 0.6) ** 3 ) * b.Pr_particle[t, x] ) # Gas-solid heat transfer coefficient @self.Constraint( self.flowsheet().time, self.length_domain, doc="Gas-solid heat transfer coefficient", ) def gas_solid_htc_eqn(b, t, x): return ( b.gas_solid_htc[t, x] * pyunits.convert( b.solid_phase.properties[t, x].params.particle_dia, to_units=units_meta_gas("length"), ) == b.Nu_particle[t, x] * b.gas_phase.properties[t, x].therm_cond ) # Gas phase - gas to solid heat transfer @self.Constraint( self.flowsheet().time, self.length_domain, doc="Gas phase - gas to solid heat transfer", ) def gas_phase_heat_transfer(b, t, x): return b.gas_phase.heat[t, x] * pyunits.convert( b.solid_phase.properties[t, x].params.particle_dia, to_units=units_meta_gas("length"), ) == -6 * b.gas_solid_htc[t, x] * ( b.gas_phase.properties[t, x].temperature - pyunits.convert( b.solid_phase.properties[t, x].temperature, to_units=units_meta_gas("temperature"), ) ) * pyunits.convert( b.solid_phase.area[t, x], to_units=units_meta_gas("area") ) elif self.config.energy_balance_type == EnergyBalanceType.none: # If energy balance is none fix gas and solid temperatures to # solid inlet temperature @self.Constraint( self.flowsheet().time, self.length_domain, doc="Isothermal gas phase constraint", ) def isothermal_gas_phase(b, t, x): if x == self.length_domain.first(): return Constraint.Skip else: return ( b.gas_phase.properties[t, x].temperature == b.solid_inlet.temperature[t] ) @self.Constraint( self.flowsheet().time, self.length_domain, doc="Isothermal solid phase constraint", ) def isothermal_solid_phase(b, t, x): if x == self.length_domain.last(): return Constraint.Skip else: return ( b.solid_phase.properties[t, x].temperature == b.solid_inlet.temperature[t] ) # ========================================================================= # Model initialization routine
[docs] def initialize_build( blk, gas_phase_state_args=None, solid_phase_state_args=None, outlvl=idaeslog.NOTSET, solver=None, optarg=None, ): """ Initialization routine for MB unit. Keyword Arguments: gas_phase_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 = None). solid_phase_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 = None). 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, use default solver) Returns: None """ # Set up logger for initialization and solve 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) # --------------------------------------------------------------------- # local aliases used to shorten object names gas_phase = blk.config.gas_phase_config solid_phase = blk.config.solid_phase_config # Keep all unit model geometry constraints, derivative_var constraints, # and property block constraints active. Additionally, in control # volumes - keep conservation linking constraints and # holdup calculation (for dynamic flowsheets) constraints active geometry_constraints_terms = [ "bed_area_eqn", "solid_phase_area", "gas_phase_area", "gas_phase_length", "solid_phase_length", ] endswith_terms = ( "_disc_eq", "linking_constraint", "linking_constraints", "_holdup_calculation", ) startswith_terms = "properties" for c in blk.component_objects(Constraint, descend_into=True): if ( not c.parent_block().local_name.startswith(startswith_terms) and not c.local_name.endswith(endswith_terms) and c.local_name not in geometry_constraints_terms ): c.deactivate() # --------------------------------------------------------------------- # Initialize thermophysical property constraints init_log.info("Initialize Thermophysical Properties") # Initialize gas_phase block gas_phase_flags = blk.gas_phase.properties.initialize( state_args=gas_phase_state_args, outlvl=outlvl, optarg=optarg, solver=solver, hold_state=True, ) # Initialize solid_phase properties block solid_phase_flags = blk.solid_phase.properties.initialize( state_args=solid_phase_state_args, outlvl=outlvl, optarg=optarg, solver=solver, hold_state=True, ) init_log.info_high("Initialization Step 1 Complete.") # --------------------------------------------------------------------- # Initialize hydrodynamics (gas velocity) for t in blk.flowsheet().time: for x in blk.length_domain: calculate_variable_from_constraint( blk.velocity_superficial_gas[t, x], blk.gas_super_vel[t, x] ) blk.gas_super_vel.activate() init_log.info("Initialize Hydrodynamics") with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: results = opt.solve(blk, tee=slc.tee) if check_optimal_termination(results): init_log.info_high( "Initialization Step 2 {}.".format(idaeslog.condition(results)) ) else: _log.warning("{} Initialization Step 2 Failed.".format(blk.name)) # --------------------------------------------------------------------- # Initialize mass balance - no reaction and no pressure drop # Unfix material balance state variables (including particle porosity) # but keep other states fixed blk.gas_phase.properties.release_state(gas_phase_flags) blk.solid_phase.properties.release_state(solid_phase_flags) for t in blk.flowsheet().time: for x in blk.length_domain: blk.gas_phase.properties[t, x].pressure.fix() blk.gas_phase.properties[t, x].temperature.fix() blk.solid_phase.properties[t, x].temperature.fix() blk.gas_phase.material_balances.activate() if gas_phase.reaction_package is not None: for t in blk.flowsheet().time: gas_rxn_gen = blk.gas_phase.rate_reaction_generation for x in blk.length_domain: for p in gas_phase.property_package.phase_list: for j in gas_phase.property_package.component_list: (gas_rxn_gen[t, x, p, j].fix(0.0)) blk.solid_phase.material_balances.activate() blk.solid_super_vel.activate() if solid_phase.reaction_package is not None: for t in blk.flowsheet().time: solid_rxn_gen = blk.solid_phase.rate_reaction_generation for x in blk.length_domain: for p in solid_phase.property_package.phase_list: for j in solid_phase.property_package.component_list: (solid_rxn_gen[t, x, p, j].fix(0.0)) # Gas side heterogeneous rate reaction generation for x in blk.length_domain: for p in gas_phase.property_package.phase_list: for j in gas_phase.property_package.component_list: (blk.gas_phase.mass_transfer_term[t, x, p, j].fix(0.0)) init_log.info("Initialize Mass Balances") init_log.info_high( "initialize mass balances - no reactions and no pressure drop" ) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: results = opt.solve(blk, tee=slc.tee) if check_optimal_termination(results): init_log.info_high( "Initialization Step 3a {}.".format(idaeslog.condition(results)) ) else: _log.warning("{} Initialization Step 3a Failed.".format(blk.name)) # Initialize mass balance - with reaction and no pressure drop if gas_phase.reaction_package is not None: # local aliases used to shorten object names gas_rxn_gen = blk.gas_phase.rate_reaction_generation gas_phase_stoichiometry_eqn = ( blk.gas_phase.rate_reaction_stoichiometry_constraint ) # Initialize reaction property package blk.gas_phase.reactions.activate() for t in blk.flowsheet().time: for x in blk.length_domain: obj = blk.gas_phase.reactions[t, x] for c in obj.component_objects(Constraint, descend_into=False): c.activate() blk.gas_phase.reactions.initialize( outlvl=outlvl, optarg=optarg, solver=solver ) for t in blk.flowsheet().time: for x in blk.length_domain: for r in gas_phase.reaction_package.rate_reaction_idx: calculate_variable_from_constraint( blk.gas_phase.rate_reaction_extent[t, x, r], blk.gas_phase_config_rxn_ext[t, x, r], ) for p in gas_phase.property_package.phase_list: for j in gas_phase.property_package.component_list: (gas_rxn_gen[t, x, p, j].unfix()) if not ( ( blk.gas_phase.config.transformation_scheme != "FORWARD" and x == blk.length_domain.first() ) or ( blk.gas_phase.config.transformation_scheme == "FORWARD" and x == blk.length_domain.last() ) ): calculate_variable_from_constraint( gas_rxn_gen[t, x, p, j], gas_phase_stoichiometry_eqn[t, x, p, j], ) gas_phase_stoichiometry_eqn.activate() blk.gas_phase_config_rxn_ext.activate() if solid_phase.reaction_package is not None: # local aliases used to shorten object names solid_rxn_gen = blk.solid_phase.rate_reaction_generation solid_phase_stoichiometry_eqn = ( blk.solid_phase.rate_reaction_stoichiometry_constraint ) gas_mass_transfer_term = blk.gas_phase.mass_transfer_term # Initialize reaction property package blk.solid_phase.reactions.activate() for t in blk.flowsheet().time: for x in blk.length_domain: obj = blk.solid_phase.reactions[t, x] for c in obj.component_objects(Constraint, descend_into=False): c.activate() blk.solid_phase.reactions.initialize( outlvl=outlvl, optarg=optarg, solver=solver ) for t in blk.flowsheet().time: for x in blk.length_domain: for p in gas_phase.property_package.phase_list: for j in gas_phase.property_package.component_list: (gas_mass_transfer_term[t, x, p, j].unfix()) calculate_variable_from_constraint( gas_mass_transfer_term[t, x, p, j], blk.gas_comp_hetero_rxn[t, x, p, j], ) for x in blk.length_domain: for r in solid_phase.reaction_package.rate_reaction_idx: calculate_variable_from_constraint( blk.solid_phase.rate_reaction_extent[t, x, r], blk.solid_phase_config_rxn_ext[t, x, r], ) for p in solid_phase.property_package.phase_list: for j in solid_phase.property_package.component_list: (solid_rxn_gen[t, x, p, j].unfix()) if not ( ( blk.solid_phase.config.transformation_scheme != "FORWARD" and x == blk.length_domain.first() ) or ( blk.solid_phase.config.transformation_scheme == "FORWARD" and x == blk.length_domain.last() ) ): calculate_variable_from_constraint( solid_rxn_gen[t, x, p, j], solid_phase_stoichiometry_eqn[t, x, p, j], ) blk.gas_comp_hetero_rxn.activate() blk.solid_phase.rate_reaction_stoichiometry_constraint.activate() blk.solid_phase_config_rxn_ext.activate() if ( gas_phase.reaction_package is not None or solid_phase.reaction_package is not None ): init_log.info_high( "initialize mass balances - with reactions and no pressure drop" ) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: results = opt.solve(blk, tee=slc.tee) if check_optimal_termination(results): init_log.info_high( "Initialization Step 3b {}.".format(idaeslog.condition(results)) ) else: _log.warning("{} Initialization Step 3b Failed.".format(blk.name)) # Initialize mass balance - with pressure drop for t in blk.flowsheet().time: for x in blk.length_domain: # Unfix all pressure variables except at the inlet if blk.gas_phase.properties[t, x].config.defined_state is False: blk.gas_phase.properties[t, x].pressure.unfix() blk.gas_phase.pressure_balance.activate() if blk.config.has_pressure_change: blk.gas_phase_config_pressure_drop.activate() for t in blk.flowsheet().time: for x in blk.length_domain: calculate_variable_from_constraint( blk.gas_phase.deltaP[t, x], blk.gas_phase_config_pressure_drop[t, x], ) init_log.info_high( "initialize mass balances - with reactions and pressure drop" ) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: results = opt.solve(blk, tee=slc.tee) if check_optimal_termination(results): init_log.info_high( "Initialization Step 3c {}.".format(idaeslog.condition(results)) ) else: _log.warning("{} Initialization Step 3c Failed.".format(blk.name)) # --------------------------------------------------------------------- # Initialize energy balance if blk.config.energy_balance_type != EnergyBalanceType.none: # Initialize dimensionless numbers, # mass and heat transfer coefficients for t in blk.flowsheet().time: for x in blk.length_domain: calculate_variable_from_constraint( blk.Re_particle[t, x], blk.reynolds_number_particle[t, x] ) calculate_variable_from_constraint( blk.Pr_particle[t, x], blk.prandtl_number[t, x] ) calculate_variable_from_constraint( blk.Nu_particle[t, x], blk.nusselt_number_particle[t, x] ) calculate_variable_from_constraint( blk.gas_solid_htc[t, x], blk.gas_solid_htc_eqn[t, x] ) calculate_variable_from_constraint( blk.gas_phase.heat[t, x], blk.gas_phase_heat_transfer[t, x] ) calculate_variable_from_constraint( blk.solid_phase.heat[t, x], blk.solid_phase_heat_transfer[t, x] ) # Unfix temperatures for t in blk.flowsheet().time: for x in blk.length_domain: # Unfix all gas temperature variables except at the inlet if blk.gas_phase.properties[t, x].config.defined_state is False: blk.gas_phase.properties[t, x].temperature.unfix() for x in blk.length_domain: # Unfix all solid temperature variables except at the inlet if blk.solid_phase.properties[t, x].config.defined_state is False: blk.solid_phase.properties[t, x].temperature.unfix() blk.reynolds_number_particle.activate() blk.prandtl_number.activate() blk.nusselt_number_particle.activate() blk.gas_solid_htc_eqn.activate() # Activate gas phase energy balance equations blk.gas_phase_heat_transfer.activate() blk.gas_phase.enthalpy_balances.activate() # Activate solid phase energy balance equations blk.solid_phase_heat_transfer.activate() blk.solid_phase.enthalpy_balances.activate() init_log.info("Initialize Energy Balances") with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: results = opt.solve(blk, tee=slc.tee) if check_optimal_termination(results): init_log.info_high( "Initialization Step 4 {}.".format(idaeslog.condition(results)) ) else: raise InitializationError( f"{blk.name} failed to initialize successfully. Please " f"check the output logs for more information." ) # Initialize energy balance if blk.config.energy_balance_type == EnergyBalanceType.none: for t in blk.flowsheet().time: for x in blk.length_domain: # Unfix all gas temperature variables except at the inlet if blk.gas_phase.properties[t, x].config.defined_state is False: blk.gas_phase.properties[t, x].temperature.unfix() for x in blk.length_domain: # Unfix all solid temperature variables except at the inlet if blk.solid_phase.properties[t, x].config.defined_state is False: blk.solid_phase.properties[t, x].temperature.unfix() blk.isothermal_gas_phase.activate() blk.isothermal_solid_phase.activate() init_log.info("Initialize Energy Balances") with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: results = opt.solve(blk, tee=slc.tee) if check_optimal_termination(results): init_log.info_high( "Initialization Step 4 {}.".format(idaeslog.condition(results)) ) else: raise InitializationError( f"{blk.name} failed to initialize successfully. Please " f"check the output logs for more information." )
def calculate_scaling_factors(self): super().calculate_scaling_factors() # local aliases used to shorten object names gas_phase = self.config.gas_phase_config # Get units meta data from property packages units_meta_gas = gas_phase.property_package.get_metadata().get_derived_units # scale some variables if hasattr(self, "bed_diameter"): if iscale.get_scaling_factor(self.bed_diameter) is None: sf = 1 / value(self.bed_diameter) iscale.set_scaling_factor(self.bed_diameter, 1e-1 * sf) if hasattr(self, "bed_area"): if iscale.get_scaling_factor(self.bed_area) is None: sf = 1 / value(constants.pi * (0.5 * self.bed_diameter) ** 2) iscale.set_scaling_factor(self.bed_area, 1e-1 * sf) if hasattr(self.gas_phase, "area"): if iscale.get_scaling_factor(self.gas_phase.area) is None: sf = iscale.get_scaling_factor(self.bed_area) iscale.set_scaling_factor(self.gas_phase.area, sf) if hasattr(self.solid_phase, "area"): if iscale.get_scaling_factor(self.solid_phase.area) is None: sf = iscale.get_scaling_factor(self.bed_area) iscale.set_scaling_factor(self.solid_phase.area, sf) if self.config.energy_balance_type != EnergyBalanceType.none: if hasattr(self, "Re_particle"): for (t, x), v in self.Re_particle.items(): if iscale.get_scaling_factor(v) is None: sf1 = iscale.get_scaling_factor( self.gas_phase.properties[t, x].dens_mass ) sf2 = 1 / value( pyunits.convert( self.solid_phase.properties[t, x].params.particle_dia, to_units=units_meta_gas("length"), ) ) iscale.set_scaling_factor(v, 1e-1 * sf1 * sf2) if hasattr(self, "Pr_particle"): for (t, x), v in self.Pr_particle.items(): if iscale.get_scaling_factor(v) is None: sf1 = iscale.get_scaling_factor( self.gas_phase.properties[t, x].cp_mass ) sf2 = iscale.get_scaling_factor( self.gas_phase.properties[t, x].visc_d ) iscale.set_scaling_factor(v, sf1 * sf2) if hasattr(self, "Nu_particle"): for (t, x), v in self.Nu_particle.items(): if iscale.get_scaling_factor(v) is None: sf = 1 / ( value( ( ( 2.0 + 1.1 * ( smooth_abs(self.Re_particle[t, x], self.eps) ** 0.6 ) ** 3 ) * self.Pr_particle[t, x] ) ** (1 / 3) ) ) iscale.set_scaling_factor(v, 1e-1 * sf) if hasattr(self, "gas_solid_htc"): if iscale.get_scaling_factor(self.gas_solid_htc) is None: for (t, x), v in self.gas_solid_htc.items(): sf1 = iscale.get_scaling_factor(self.Nu_particle[t, x]) sf2 = iscale.get_scaling_factor( self.gas_phase.properties[t, x].therm_cond ) iscale.set_scaling_factor(v, sf1 * sf2) # scale heat variables in CV1D for (t, x), v in self.gas_phase.heat.items(): sf1 = iscale.get_scaling_factor( self.gas_phase.properties[t, x].enth_mol ) sf2 = iscale.get_scaling_factor( self.gas_phase.properties[t, x].flow_mol ) iscale.set_scaling_factor(v, sf1 * sf2) for (t, x), v in self.solid_phase.heat.items(): sf1 = iscale.get_scaling_factor( self.solid_phase.properties[t, x].enth_mass ) sf2 = iscale.get_scaling_factor( self.solid_phase.properties[t, x].flow_mass ) iscale.set_scaling_factor(v, sf1 * sf2) # Scale some constraints if hasattr(self, "bed_area_eqn"): for c in self.bed_area_eqn.values(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.bed_area), overwrite=False ) if hasattr(self, "gas_phase_area"): for (t, x), c in self.gas_phase_area.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.bed_area), overwrite=False ) if hasattr(self, "solid_phase_area"): for (t, x), c in self.solid_phase_area.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.bed_area), overwrite=False ) if hasattr(self, "gas_super_vel"): for (t, x), c in self.gas_super_vel.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.gas_phase.properties[t, x].flow_mol), overwrite=False, ) if hasattr(self, "solid_super_vel"): for (t, x), c in self.solid_super_vel.items(): iscale.constraint_scaling_transform( c, 1e-1 * iscale.get_scaling_factor( self.solid_phase.properties[t, x].flow_mass ), overwrite=False, ) if hasattr(self, "gas_phase_config_pressure_drop"): for (t, x), c in self.gas_phase_config_pressure_drop.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.gas_phase.deltaP[t, x]), overwrite=False, ) if hasattr(self, "gas_phase_config_rxn_ext"): for (t, x, r), c in self.gas_phase_config_rxn_ext.items(): sf1 = iscale.get_scaling_factor( self.gas_phase.reactions[t, x].reaction_rate[r] ) sf2 = iscale.get_scaling_factor(self.bed_area) iscale.constraint_scaling_transform(c, sf1 * sf2, overwrite=False) if hasattr(self, "solid_phase_config_rxn_ext"): for (t, x, r), c in self.solid_phase_config_rxn_ext.items(): sf1 = iscale.get_scaling_factor( self.solid_phase.reactions[t, x].reaction_rate[r] ) sf2 = iscale.get_scaling_factor(self.bed_area) iscale.constraint_scaling_transform(c, sf1 * sf2, overwrite=False) if hasattr(self, "gas_comp_hetero_rxn"): for (t, x, p, j), c in self.gas_comp_hetero_rxn.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor( self.gas_phase.mass_transfer_term[t, x, p, j] ), overwrite=False, ) if self.config.energy_balance_type != EnergyBalanceType.none: if hasattr(self, "reynolds_number_particle"): for (t, x), c in self.reynolds_number_particle.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.Re_particle[t, x]), overwrite=False, ) if hasattr(self, "prandtl_number"): for (t, x), c in self.prandtl_number.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.Pr_particle[t, x]), overwrite=False, ) if hasattr(self, "nusselt_number_particle"): for (t, x), c in self.nusselt_number_particle.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.Nu_particle[t, x]), overwrite=False, ) if hasattr(self, "gas_solid_htc_eqn"): for (t, x), c in self.gas_solid_htc_eqn.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.gas_solid_htc[t, x]), overwrite=False, ) if hasattr(self, "gas_phase_heat_transfer"): for (t, x), c in self.gas_phase_heat_transfer.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.gas_phase.heat[t, x]), overwrite=False, ) if hasattr(self, "solid_phase_heat_transfer"): for (t, x), c in self.solid_phase_heat_transfer.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor(self.solid_phase.heat[t, x]), overwrite=False, ) elif self.config.energy_balance_type == EnergyBalanceType.none: if hasattr(self, "isothermal_gas_phase"): for (t, x), c in self.isothermal_gas_phase.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor( self.gas_phase.properties[t, x].temperature ), overwrite=False, ) if hasattr(self, "isothermal_solid_phase"): for (t, x), c in self.isothermal_solid_phase.items(): iscale.constraint_scaling_transform( c, iscale.get_scaling_factor( self.gas_phase.properties[t, x].temperature ), overwrite=False, )
[docs] def results_plot(blk): """ Plot method for common moving bed variables Variables plotted: Tg : Temperature in gas phase Ts : Temperature in solid phase vg : Superficial gas velocity P : Pressure in gas phase Ftotal : Total molar flowrate of gas Mtotal : Total mass flowrate of solid Cg : Concentration of gas components in the gas phase y_frac : Mole fraction of gas components in the gas phase x_frac : Mass fraction of solid components in the solid phase """ print() print( "================================= Reactor plots ===============" "==================" ) # local aliases used to shorten object names gas_phase = blk.config.gas_phase_config solid_phase = blk.config.solid_phase_config Tg = [] Ts = [] P = [] Ftotal = [] Mtotal = [] vg = [] for t in blk.flowsheet().time: for x in blk.gas_phase.length_domain: vg.append(value(blk.velocity_superficial_gas[t, x])) fig_vg = plt.figure(1) plt.plot(blk.gas_phase.length_domain, vg, label="vg") plt.legend(loc=0, ncol=2) plt.grid() plt.xlabel("Bed height [-]") plt.ylabel("Superficial gas velocity [m/s]") fig_vg.savefig("superficial_vel.png") # Pressure profile for t in blk.flowsheet().time: for x in blk.gas_phase.length_domain: P.append(blk.gas_phase.properties[t, x].pressure.value) fig_P = plt.figure(2) plt.plot(blk.gas_phase.length_domain, P, label="P") plt.legend(loc=0, ncol=2) plt.grid() plt.xlabel("Bed height [-]") plt.ylabel("Total Pressure [Pa]") fig_P.savefig("Pressure.png") # Temperature profile for t in blk.flowsheet().time: for x in blk.gas_phase.length_domain: Tg.append(blk.gas_phase.properties[t, x].temperature.value) for x in blk.solid_phase.length_domain: Ts.append(blk.solid_phase.properties[t, x].temperature.value) fig_T = plt.figure(3) plt.plot(blk.gas_phase.length_domain, Tg, label="Tg") plt.plot(blk.solid_phase.length_domain, Ts, label="Ts") plt.legend(loc=0, ncol=2) plt.grid() plt.xlabel("Bed height [-]") plt.ylabel("Temperature [K]") fig_T.savefig("Temperature.png") # Bulk gas phase total molar flow rate for t in blk.flowsheet().time: for x in blk.gas_phase.length_domain: Ftotal.append(blk.gas_phase.properties[t, x].flow_mol.value) fig_Ftotal = plt.figure(4) plt.plot(blk.gas_phase.length_domain, Ftotal) plt.grid() plt.xlabel("Bed height [-]") plt.ylabel("Total molar gas flow rate [mol/s]") fig_Ftotal.savefig("Total_gas_flow.png") # Bulk solid phase total mass flow rate for t in blk.flowsheet().time: for x in blk.solid_phase.length_domain: Mtotal.append(blk.solid_phase.properties[t, x].flow_mass.value) fig_Mtotal = plt.figure(5) plt.plot(blk.solid_phase.length_domain, Mtotal) plt.grid() plt.xlabel("Bed height [-]") plt.ylabel("Solid total mass flow rate [kg/s]") fig_Mtotal.savefig("Total_solid_flow.png") # Gas phase mole fractions for t in blk.flowsheet().time: for j in gas_phase.property_package.component_list: y_frac = [] for x in blk.gas_phase.length_domain: y_frac.append( value(blk.gas_phase.properties[t, x].mole_frac_comp[j]) ) fig_y = plt.figure(6) plt.plot(blk.gas_phase.length_domain, y_frac, label=j) plt.legend(loc=0, ncol=len(gas_phase.property_package.component_list)) plt.grid() plt.xlabel("Bed height [-]") plt.ylabel("y_frac [-]") fig_y.savefig("Gas_mole_fractions.png") # Solid phase mass fractions for t in blk.flowsheet().time: for j in solid_phase.property_package.component_list: x_frac = [] for x in blk.solid_phase.length_domain: x_frac.append( value(blk.solid_phase.properties[t, x].mass_frac_comp[j]) ) fig_x = plt.figure(7) plt.plot(blk.solid_phase.length_domain, x_frac, label=j) plt.legend(loc=0, ncol=len(solid_phase.property_package.component_list)) plt.grid() plt.xlabel("Bed height [-]") plt.ylabel("x_frac [-]") fig_x.savefig("Solid_mass_fractions.png") # Gas phase concentrations for t in blk.flowsheet().time: for j in gas_phase.property_package.component_list: Cg = [] for x in blk.gas_phase.length_domain: Cg.append(blk.gas_phase.properties[t, x].dens_mol_comp[j].value) fig_Cg = plt.figure(8) plt.plot(blk.gas_phase.length_domain, Cg, label=j) plt.legend(loc=0, ncol=len(gas_phase.property_package.component_list)) plt.grid() plt.xlabel("Bed height [-]") plt.ylabel("Concentration [mol/m3]") fig_Cg.savefig("Gas_concentration.png")
def _get_stream_table_contents(self, time_point=0): return create_stream_table_dataframe( { "Gas Inlet": self.gas_inlet, "Gas Outlet": self.gas_outlet, "Solid Inlet": self.solid_inlet, "Solid Outlet": self.solid_outlet, }, time_point=time_point, )