class TrayColumnData(UnitModelBlockData): """ Tray Column model. """ CONFIG = ConfigBlock() CONFIG.declare( "dynamic", ConfigValue(domain=In([False]), default=False, description="Dynamic model flag - must be False", doc="""Indicates whether this model will be dynamic or not, **default** = False. Tray column units do not support dynamic behavior.""")) CONFIG.declare( "has_holdup", ConfigValue( default=False, domain=In([False]), description="Holdup construction flag - must be False", doc="""Indicates whether holdup terms should be constructed or not. **default** - False. Tray column units do not have defined volume, thus this must be False.""")) CONFIG.declare( "number_of_trays", ConfigValue(default=None, domain=In(Integers), description="Number of trays in the column", doc="""Indicates the number of trays to be constructed. **default** - None. **Valid values:** { Must be integer number.}""")) CONFIG.declare( "feed_tray_location", ConfigValue(default=None, domain=In(Integers), description="Feed tray location in the column", doc="""Indicates the number of trays to be constructed. **default** - None. **Valid values:** { Must be integer number.}""")) CONFIG.declare( "condenser_type", ConfigValue( default=CondenserType.totalCondenser, domain=In(CondenserType), description="Type of condenser flag", doc="""Indicates what type of condenser should be constructed, **default** - CondenserType.totalCondenser. **Valid values:** { **CondenserType.totalCondenser** - Incoming vapor from top tray is condensed to all liquid, **CondenserType.partialCondenser** - Incoming vapor from top tray is partially condensed to a vapor and liquid stream.}""")) CONFIG.declare( "condenser_temperature_spec", ConfigValue(default=None, domain=In(TemperatureSpec), description="Temperature spec for the condenser", doc="""Temperature specification for the condenser, **default** - TemperatureSpec.none **Valid values:** { **TemperatureSpec.none** - No spec is selected, **TemperatureSpec.atBubblePoint** - Condenser temperature set at bubble point i.e. total condenser, **TemperatureSpec.customTemperature** - Condenser temperature at user specified temperature.}""")) CONFIG.declare( "has_heat_transfer", ConfigValue(default=False, domain=Bool, description="heat duty to/from tray construction flag.", doc="""indicates if there is heat duty to/from the tray, **default** - False. **Valid values:** { **True** - include a heat duty term, **False** - exclude a heat duty term.}""")) CONFIG.declare( "has_pressure_change", ConfigValue( default=False, 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( "has_liquid_side_draw", ConfigValue( default=False, domain=Bool, description="liquid side draw construction flag.", doc="""indicates if there is a liquid side draw from all trays, **default** - False. **Valid values:** { **True** - include a liquid side draw for all trays, **False** - exclude a liquid side draw for all trays.}""")) CONFIG.declare( "has_vapor_side_draw", ConfigValue( default=False, domain=Bool, description="vapor side draw construction flag.", doc="""indicates if there is a vapor side draw from all trays, **default** - False. **Valid values:** { **True** - include a vapor side draw for all trays, **False** - exclude a vapor side draw for all trays.}""")) CONFIG.declare( "property_package", ConfigValue( default=useDefault, domain=is_physical_parameter_block, description="property package to use for control volume", doc= """property parameter object used to define property calculations, **default** - useDefault. **Valid values:** { **useDefault** - use default package from parent model or flowsheet, **PropertyParameterObject** - a PropertyParameterBlock object.}""")) CONFIG.declare( "property_package_args", ConfigBlock( implicit=True, description="arguments to use for constructing property packages", doc= """a ConfigBlock with arguments to be passed to a property block(s) and used when constructing these, **default** - None. **Valid values:** { see property package for documentation.}""")) def build(self): """Build the model. Args: None Returns: None """ # Call UnitModel.build to setup dynamics super(TrayColumnData, self).build() # Create set for constructing indexed trays if self.config.number_of_trays is not None: self.tray_index = RangeSet(1, self.config.number_of_trays) self._rectification_index = RangeSet( 1, self.config.feed_tray_location - 1) self._stripping_index = RangeSet( self.config.feed_tray_location + 1, self.config.number_of_trays) else: raise ConfigurationError("The config argument number_of_trays " "needs to specified and cannot be None.") # Add trays # TODO: # 1. Add support for multiple feed tray locations # 2. Add support for specifying which trays have side draws self.rectification_section = Tray(self._rectification_index, default={ "has_liquid_side_draw": self.config.has_liquid_side_draw, "has_vapor_side_draw": self.config.has_vapor_side_draw, "has_heat_transfer": self.config.has_heat_transfer, "has_pressure_change": self.config.has_pressure_change, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args }) self.feed_tray = Tray( default={ "is_feed_tray": True, "has_liquid_side_draw": self.config.has_liquid_side_draw, "has_vapor_side_draw": self.config.has_vapor_side_draw, "has_heat_transfer": self.config.has_heat_transfer, "has_pressure_change": self.config.has_pressure_change, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args }) self.stripping_section = Tray(self._stripping_index, default={ "has_liquid_side_draw": self.config.has_liquid_side_draw, "has_vapor_side_draw": self.config.has_vapor_side_draw, "has_heat_transfer": self.config.has_heat_transfer, "has_pressure_change": self.config.has_pressure_change, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args }) # Add condenser self.condenser = Condenser( default={ "condenser_type": self.config.condenser_type, "temperature_spec": self.config.condenser_temperature_spec, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args }) # Add reboiler self.reboiler = Reboiler( default={ "has_boilup_ratio": True, "has_pressure_change": self.config.has_pressure_change, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args }) # Add extension to the feed port self.feed = Port(extends=self.feed_tray.feed) # Add extensions to rectification section # Add extensions to stripping section # Construct arcs between trays, condenser, and reboiler self._make_rectification_arcs() self._make_stripping_arcs() self._make_feed_arcs() self._make_condenser_arcs() self._make_reboiler_arcs() TransformationFactory("network.expand_arcs").apply_to(self) def _make_rectification_arcs(self): self._rectification_stream_index = RangeSet( 1, self.config.feed_tray_location - 2) def rule_liq_stream(self, i): return { "source": self.rectification_section[i].liq_out, "destination": self.rectification_section[i + 1].liq_in } def rule_vap_stream(self, i): return { "source": self.rectification_section[i + 1].vap_out, "destination": self.rectification_section[i].vap_in } self.rectification_liq_stream = Arc(self._rectification_stream_index, rule=rule_liq_stream) self.rectification_vap_stream = Arc(self._rectification_stream_index, rule=rule_vap_stream) def _make_stripping_arcs(self): self._stripping_stream_index = RangeSet( self.config.feed_tray_location + 1, self.config.number_of_trays - 1) def rule_liq_stream(self, i): return { "source": self.stripping_section[i].liq_out, "destination": self.stripping_section[i + 1].liq_in } def rule_vap_stream(self, i): return { "source": self.stripping_section[i + 1].vap_out, "destination": self.stripping_section[i].vap_in } self.stripping_liq_stream = Arc(self._stripping_stream_index, rule=rule_liq_stream) self.stripping_vap_stream = Arc(self._stripping_stream_index, rule=rule_vap_stream) def _make_feed_arcs(self): self.feed_liq_in = Arc( source=self.rectification_section[self.config.feed_tray_location - 1].liq_out, destination=self.feed_tray.liq_in) self.feed_liq_out = Arc( source=self.feed_tray.liq_out, destination=self.stripping_section[self.config.feed_tray_location + 1].liq_in) self.feed_vap_in = Arc( source=self.stripping_section[self.config.feed_tray_location + 1].vap_out, destination=self.feed_tray.vap_in) self.feed_vap_out = Arc(source=self.feed_tray.vap_out, destination=self.rectification_section[ self.config.feed_tray_location - 1].vap_in) def _make_condenser_arcs(self): self.condenser_vap_in = Arc( source=self.rectification_section[1].vap_out, destination=self.condenser.inlet) self.condenser_reflux_out = Arc( source=self.condenser.reflux, destination=self.rectification_section[1].liq_in) def _make_reboiler_arcs(self): self.reboiler_liq_in = Arc( source=self.stripping_section[self.config.number_of_trays].liq_out, destination=self.reboiler.inlet) self.reboiler_vap_out = Arc(source=self.reboiler.vapor_reboil, destination=self.stripping_section[ self.config.number_of_trays].vap_in) def propagate_stream_state(self, source=None, destination=None): """ This method is used during initialization to propage state values between any two ports. Args: source : source port destination : destination port """ for v in source.vars: for i in destination.vars[v]: if not destination.vars[v][i].fixed: destination.vars[v][i].value = \ value(source.vars[v][i]) def initialize(self, state_args_feed=None, state_args_liq=None, state_args_vap=None, solver=None, optarg=None, outlvl=idaeslog.NOTSET): init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit") init_log.info("Begin initialization.") solverobj = get_solver(solver, optarg) feed_flags = self.feed_tray.initialize(solver=solver, optarg=optarg, outlvl=outlvl) self.propagate_stream_state(source=self.feed_tray.vap_out, destination=self.condenser.inlet) self.condenser.initialize(solver=solver, optarg=optarg, outlvl=outlvl) self.propagate_stream_state(source=self.feed_tray.liq_out, destination=self.reboiler.inlet) self.reboiler.initialize(solver=solver, optarg=optarg, outlvl=outlvl) # initialize the rectification section for i in self._rectification_index: self.propagate_stream_state( source=self.condenser.reflux, destination=self.rectification_section[i].liq_in) self.propagate_stream_state( source=self.feed_tray.vap_out, destination=self.rectification_section[i].vap_in) if i == 1: rect_liq_flags = self.rectification_section[i]. \ initialize(hold_state_liq=True, hold_state_vap=False, solver=solver, optarg=optarg, outlvl=outlvl) elif i == len(self._rectification_index): rect_vap_flags = \ self.rectification_section[i]. \ initialize(hold_state_liq=False, hold_state_vap=True, solver=solver, optarg=optarg, outlvl=outlvl) else: self.rectification_section[i].initialize(solver=solver, optarg=optarg, outlvl=outlvl) # initialize the stripping section for i in self._stripping_index: self.propagate_stream_state( source=self.feed_tray.liq_out, destination=self.stripping_section[i].liq_in) self.propagate_stream_state( source=self.reboiler.vapor_reboil, destination=self.stripping_section[i].vap_in) if i == self.config.feed_tray_location + 1: strip_liq_flags = self.stripping_section[i]. \ initialize(hold_state_liq=True, hold_state_vap=False, solver=solver, optarg=optarg, outlvl=outlvl) elif i == self.config.number_of_trays: strip_vap_flags = self.stripping_section[i]. \ initialize(hold_state_liq=False, hold_state_vap=True, solver=solver, optarg=optarg, outlvl=outlvl) else: self.stripping_section[i].initialize(solver=None, optarg=optarg, outlvl=outlvl) # For initialization purposes and to enable solving individual sections # creating a temp block. Note that this temp block is a reference to # the rectification, stripping, and feed sections. Also, expanded arcs # are added to the temp block as the initialization solve proceeds. self._temp_block = Block() self._temp_block.rectification = Block() # adding reference to the rectification section and the expanded # vapor and liquid arcs self._temp_block.rectification.trays = Reference( self.rectification_section) self._temp_block.rectification.expanded_liq_stream = Reference( self.rectification_liq_stream[:].expanded_block) self._temp_block.rectification.expanded_vap_stream = Reference( self.rectification_vap_stream[:].expanded_block) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = solverobj.solve(self._temp_block.rectification, tee=slc.tee) init_log.info("Rectification section initialization status {}.".format( idaeslog.condition(res))) self._temp_block.stripping = Block() # adding reference to the stripping section and the expanded # vapor and liquid arcs self._temp_block.stripping.trays = Reference(self.stripping_section) self._temp_block.stripping.expanded_liq_stream = Reference( self.stripping_liq_stream[:].expanded_block) self._temp_block.stripping.expanded_vap_stream = Reference( self.stripping_vap_stream[:].expanded_block) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = solverobj.solve(self._temp_block.stripping, tee=slc.tee) init_log.info("Stripping section initialization status {}.".format( idaeslog.condition(res))) # releasing the fixed inlets for the vap in to the rectification # to enable connection with the feed tray vap out self.rectification_section[len(self._rectification_index)]. \ properties_in_vap. \ release_state(flags=rect_vap_flags, outlvl=outlvl) # releasing the fixed inlets for the liq in to the stripping # to enable connection with the feed tray liq out self.stripping_section[self.config.feed_tray_location + 1]. \ properties_in_liq. \ release_state(flags=strip_liq_flags, outlvl=outlvl) # Adding the feed tray to temp block solve self._temp_block.feed_tray = Reference(self.feed_tray) self._temp_block.expanded_feed_liq_stream_in = Reference( self.feed_liq_in.expanded_block) self._temp_block.expanded_feed_liq_stream_out = Reference( self.feed_liq_out.expanded_block) self._temp_block.expanded_feed_vap_stream_in = Reference( self.feed_vap_in.expanded_block) self._temp_block.expanded_feed_vap_stream_out = Reference( self.feed_vap_out.expanded_block) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = solverobj.solve(self._temp_block, tee=slc.tee) init_log.info("Column section initialization status {}.".format( idaeslog.condition(res))) self.rectification_section[1]. \ properties_in_liq. \ release_state(flags=rect_liq_flags, outlvl=outlvl) # Adding the condenser to the temp block solve self._temp_block.condenser = Reference(self.condenser) self._temp_block.expanded_condenser_vap_in = Reference( self.condenser_vap_in.expanded_block) self._temp_block.expanded_condenser_reflux_out = Reference( self.condenser_reflux_out.expanded_block) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = solverobj.solve(self._temp_block, tee=slc.tee) init_log.info( "Column section + Condenser initialization status {}.".format( idaeslog.condition(res))) self.stripping_section[self.config.number_of_trays]. \ properties_in_vap. \ release_state(flags=strip_vap_flags, outlvl=outlvl) # Delete the _temp_block as next solve is solving the entire column. # If we add the reboiler to the temp block, it will be similar to # solving the original block. This ensures that if # initialize is triggered twice, there is no implicit replacing # component error. self.del_component(self._temp_block) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = solverobj.solve(self, tee=slc.tee) init_log.info( "Column section + Condenser + Reboiler initialization status {}.". format(idaeslog.condition(res))) # release feed tray state once initialization is complete self.feed_tray.properties_in_feed.\ release_state(flags=feed_flags, outlvl=outlvl)
class TrayColumnData(UnitModelBlockData): """ Tray Column model. """ CONFIG = ConfigBlock() CONFIG.declare( "dynamic", ConfigValue(domain=In([False]), default=False, description="Dynamic model flag - must be False", doc="""Indicates whether this model will be dynamic or not, **default** = False. Tray column units do not support dynamic behavior.""")) CONFIG.declare( "has_holdup", ConfigValue( default=False, domain=In([False]), description="Holdup construction flag - must be False", doc="""Indicates whether holdup terms should be constructed or not. **default** - False. Tray column units do not have defined volume, thus this must be False.""")) CONFIG.declare( "number_of_trays", ConfigValue(default=None, domain=In(Integers), description="Number of trays in the column", doc="""Indicates the number of trays to be constructed. **default** - None. **Valid values:** { Must be integer number.}""")) CONFIG.declare( "feed_tray_location", ConfigValue(default=None, domain=In(Integers), description="Feed tray location in the column", doc="""Indicates the number of trays to be constructed. **default** - None. **Valid values:** { Must be integer number.}""")) CONFIG.declare( "condenser_type", ConfigValue( default=CondenserType.totalCondenser, domain=In(CondenserType), description="Type of condenser flag", doc="""Indicates what type of condenser should be constructed, **default** - CondenserType.totalCondenser. **Valid values:** { **CondenserType.totalCondenser** - Incoming vapor from top tray is condensed to all liquid, **CondenserType.partialCondenser** - Incoming vapor from top tray is partially condensed to a vapor and liquid stream.}""")) CONFIG.declare( "condenser_temperature_spec", ConfigValue(default=None, domain=In(TemperatureSpec), description="Temperature spec for the condenser", doc="""Temperature specification for the condenser, **default** - TemperatureSpec.none **Valid values:** { **TemperatureSpec.none** - No spec is selected, **TemperatureSpec.atBubblePoint** - Condenser temperature set at bubble point i.e. total condenser, **TemperatureSpec.customTemperature** - Condenser temperature at user specified temperature.}""")) CONFIG.declare( "has_heat_transfer", ConfigValue(default=False, domain=In([True, False]), description="heat duty to/from tray construction flag.", doc="""indicates if there is heat duty to/from the tray, **default** - False. **Valid values:** { **True** - include a heat duty term, **False** - exclude a heat duty term.}""")) CONFIG.declare( "has_pressure_change", ConfigValue( default=False, domain=In([True, False]), 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( "has_liquid_side_draw", ConfigValue( default=False, domain=In([True, False]), description="liquid side draw construction flag.", doc="""indicates if there is a liquid side draw from all trays, **default** - False. **Valid values:** { **True** - include a liquid side draw for all trays, **False** - exclude a liquid side draw for all trays.}""")) CONFIG.declare( "has_vapor_side_draw", ConfigValue( default=False, domain=In([True, False]), description="vapor side draw construction flag.", doc="""indicates if there is a vapor side draw from all trays, **default** - False. **Valid values:** { **True** - include a vapor side draw for all trays, **False** - exclude a vapor side draw for all trays.}""")) CONFIG.declare( "property_package", ConfigValue( default=useDefault, domain=is_physical_parameter_block, description="property package to use for control volume", doc= """property parameter object used to define property calculations, **default** - useDefault. **Valid values:** { **useDefault** - use default package from parent model or flowsheet, **PropertyParameterObject** - a PropertyParameterBlock object.}""")) CONFIG.declare( "property_package_args", ConfigBlock( implicit=True, description="arguments to use for constructing property packages", doc= """a ConfigBlock with arguments to be passed to a property block(s) and used when constructing these, **default** - None. **Valid values:** { see property package for documentation.}""")) def build(self): """Build the model. Args: None Returns: None """ # Call UnitModel.build to setup dynamics super(TrayColumnData, self).build() # Create set for constructing indexed trays if self.config.number_of_trays is not None: self.tray_index = RangeSet(1, self.config.number_of_trays) else: raise ConfigurationError("The config argument number_of_trays " "needs to specified and cannot be None.") # Add trays # TODO: # 1. Add support for multiple feed tray locations # 2. Add support for specifying which trays have side draws self.tray = Tray(self.tray_index, default={ "has_liquid_side_draw": self.config.has_liquid_side_draw, "has_vapor_side_draw": self.config.has_vapor_side_draw, "has_heat_transfer": self.config.has_heat_transfer, "has_pressure_change": self.config.has_pressure_change, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args }, initialize={ self.config.feed_tray_location: { "is_feed_tray": True, "has_liquid_side_draw": self.config.has_liquid_side_draw, "has_vapor_side_draw": self.config.has_vapor_side_draw, "has_heat_transfer": self.config.has_heat_transfer, "has_pressure_change": self.config.has_pressure_change, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args } }) # Add condenser self.condenser = Condenser( default={ "condenser_type": self.config.condenser_type, "temperature_spec": self.config.condenser_temperature_spec, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args }) # Add reboiler self.reboiler = Reboiler( default={ "has_boilup_ratio": True, "has_pressure_change": self.config.has_pressure_change, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args }) # Add extension to the feed port self.feed = Port( extends=self.tray[self.config.feed_tray_location].feed) # Construct arcs between trays, condenser, and reboiler self._make_arcs() TransformationFactory("network.expand_arcs").apply_to(self) def _make_arcs(self): # make arcs self.liq_stream_index = RangeSet(0, self.config.number_of_trays) self.vap_stream_index = RangeSet(1, self.config.number_of_trays + 1) def rule_liq_stream(self, i): if i == 0: return { "source": self.condenser.reflux, "destination": self.tray[i + 1].liq_in } elif i == self.config.number_of_trays: return { "source": self.tray[i].liq_out, "destination": self.reboiler.inlet } else: return { "source": self.tray[i].liq_out, "destination": self.tray[i + 1].liq_in } def rule_vap_stream(self, i): if i == 1: return { "source": self.tray[i].vap_out, "destination": self.condenser.inlet } elif i == self.config.number_of_trays + 1: return { "source": self.reboiler.vapor_reboil, "destination": self.tray[i - 1].vap_in } else: return { "source": self.tray[i].vap_out, "destination": self.tray[i - 1].vap_in } self.liq_stream = Arc(self.liq_stream_index, rule=rule_liq_stream) self.vap_stream = Arc(self.vap_stream_index, rule=rule_vap_stream) def propagate_stream_state(self, source=None, destination=None): """ This method is used during initialization to propage state values between any two ports. Args: source : source port destination : destination port """ for v in source.vars: for i in destination.vars[v]: if not destination.vars[v][i].fixed: destination.vars[v][i].value = value(source.vars[v][i]) def initialize(self, state_args_feed=None, state_args_liq=None, state_args_vap=None, solver=None, outlvl=idaeslog.NOTSET): init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit") init_log.info("Begin initialization.") if solver is None: init_log.warning("Solver not provided. Default solver(ipopt) " "being used for initialization.") solver = get_default_solver() feed_flags = self.tray[self.config.feed_tray_location].initialize() self.propagate_stream_state( source=self.tray[self.config.feed_tray_location].vap_out, destination=self.condenser.inlet) self.condenser.initialize() self.propagate_stream_state( source=self.tray[self.config.feed_tray_location].liq_out, destination=self.reboiler.inlet) self.reboiler.initialize() for i in self.tray_index: self.propagate_stream_state(source=self.condenser.reflux, destination=self.tray[i].liq_in) self.propagate_stream_state(source=self.reboiler.vapor_reboil, destination=self.tray[i].vap_in) self.tray[i].initialize() with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = solver.solve(self, tee=slc.tee) init_log.info("Column initialization status {}.".format( idaeslog.condition(res))) # release feed tray state once initialization is complete self.tray[self.config.feed_tray_location].properties_in_feed.\ release_state(flags=feed_flags, outlvl=outlvl)