def build(self): """ Building model Args: None Returns: None """ ######################################################################## # Call UnitModel.build to setup dynamics and configure # ######################################################################## super().build() self._process_config() config = self.config ######################################################################## # Add control volumes # ######################################################################## hot_side = _make_heater_control_volume( self, config.hot_side_name, config.hot_side_config, dynamic=config.dynamic, has_holdup=config.has_holdup, ) cold_side = _make_heater_control_volume( self, config.cold_side_name, config.cold_side_config, dynamic=config.dynamic, has_holdup=config.has_holdup, ) # Add references to the hot side and cold side, so that we have solid # names to refer to internally. side_1 and side_2 also maintain # compatability with older models. Using add_object_reference keeps # these from showing up when you iterate through pyomo compoents in a # model, so only the user specified control volume names are "seen" if not hasattr(self, "side_1"): add_object_reference(self, "side_1", hot_side) if not hasattr(self, "side_2"): add_object_reference(self, "side_2", cold_side) if not hasattr(self, "hot_side"): add_object_reference(self, "hot_side", hot_side) if not hasattr(self, "cold_side"): add_object_reference(self, "cold_side", cold_side) ######################################################################## # Add variables # ######################################################################## # Use hot side units as basis s1_metadata = config.hot_side_config.property_package.get_metadata() q_units = s1_metadata.get_derived_units("power") u_units = s1_metadata.get_derived_units("heat_transfer_coefficient") a_units = s1_metadata.get_derived_units("area") temp_units = s1_metadata.get_derived_units("temperature") u = self.overall_heat_transfer_coefficient = Var( self.flowsheet().config.time, domain=PositiveReals, initialize=100.0, doc="Overall heat transfer coefficient", units=u_units) a = self.area = Var(domain=PositiveReals, initialize=1000.0, doc="Heat exchange area", units=a_units) self.delta_temperature_in = Var( self.flowsheet().config.time, initialize=10.0, doc="Temperature difference at the hot inlet end", units=temp_units) self.delta_temperature_out = Var( self.flowsheet().config.time, initialize=10.1, doc="Temperature difference at the hot outlet end", units=temp_units) if self.config.flow_pattern == HeatExchangerFlowPattern.crossflow: self.crossflow_factor = Var( self.flowsheet().config.time, initialize=1.0, doc="Factor to adjust coutercurrent flow heat " "transfer calculation for cross flow.", ) f = self.crossflow_factor self.heat_duty = Reference(cold_side.heat) ######################################################################## # Add ports # ######################################################################## i1 = self.add_inlet_port(name=f"{config.hot_side_name}_inlet", block=hot_side, doc="Hot side inlet") i2 = self.add_inlet_port(name=f"{config.cold_side_name}_inlet", block=cold_side, doc="Cold side inlet") o1 = self.add_outlet_port(name=f"{config.hot_side_name}_outlet", block=hot_side, doc="Hot side outlet") o2 = self.add_outlet_port(name=f"{config.cold_side_name}_outlet", block=cold_side, doc="Cold side outlet") # Using Andrew's function for now. I want these port names for backward # compatablity, but I don't want them to appear if you iterate throught # components and add_object_reference hides them from Pyomo. if not hasattr(self, "inlet_1"): add_object_reference(self, "inlet_1", i1) if not hasattr(self, "inlet_2"): add_object_reference(self, "inlet_2", i2) if not hasattr(self, "outlet_1"): add_object_reference(self, "outlet_1", o1) if not hasattr(self, "outlet_2"): add_object_reference(self, "outlet_2", o2) if not hasattr(self, "hot_inlet"): add_object_reference(self, "hot_inlet", i1) if not hasattr(self, "cold_inlet"): add_object_reference(self, "cold_inlet", i2) if not hasattr(self, "hot_outlet"): add_object_reference(self, "hot_outlet", o1) if not hasattr(self, "cold_outlet"): add_object_reference(self, "cold_outlet", o2) ######################################################################## # Add end temperature differnece constraints # ######################################################################## @self.Constraint(self.flowsheet().config.time) def delta_temperature_in_equation(b, t): if b.config.flow_pattern == HeatExchangerFlowPattern.cocurrent: return (b.delta_temperature_in[t] == hot_side.properties_in[t].temperature - pyunits.convert(cold_side.properties_in[t].temperature, to_units=temp_units)) else: return ( b.delta_temperature_in[t] == hot_side.properties_in[t].temperature - pyunits.convert(cold_side.properties_out[t].temperature, to_units=temp_units)) @self.Constraint(self.flowsheet().config.time) def delta_temperature_out_equation(b, t): if b.config.flow_pattern == HeatExchangerFlowPattern.cocurrent: return ( b.delta_temperature_out[t] == hot_side.properties_out[t].temperature - pyunits.convert(cold_side.properties_out[t].temperature, to_units=temp_units)) else: return (b.delta_temperature_out[t] == hot_side.properties_out[t].temperature - pyunits.convert(cold_side.properties_in[t].temperature, to_units=temp_units)) ######################################################################## # Add a unit level energy balance # ######################################################################## @self.Constraint(self.flowsheet().config.time) def unit_heat_balance(b, t): return 0 == (hot_side.heat[t] + pyunits.convert(cold_side.heat[t], to_units=q_units)) ######################################################################## # Add delta T calculations using callack function, lots of options, # # and users can provide their own if needed # ######################################################################## config.delta_temperature_callback(self) ######################################################################## # Add Heat transfer equation # ######################################################################## deltaT = self.delta_temperature @self.Constraint(self.flowsheet().config.time) def heat_transfer_equation(b, t): if self.config.flow_pattern == HeatExchangerFlowPattern.crossflow: return pyunits.convert(self.heat_duty[t], to_units=q_units) == (f[t] * u[t] * a * deltaT[t]) else: return pyunits.convert(self.heat_duty[t], to_units=q_units) == (u[t] * a * deltaT[t]) ######################################################################## # Add symbols for LaTeX equation rendering # ######################################################################## self.overall_heat_transfer_coefficient.latex_symbol = "U" self.area.latex_symbol = "A" hot_side.heat.latex_symbol = "Q_1" cold_side.heat.latex_symbol = "Q_2" self.delta_temperature.latex_symbol = "\\Delta T"
def build(self): """ Building model Args: None Returns: None """ ######################################################################## # Call UnitModel.build to setup dynamics and configure # ######################################################################## super().build() self._process_config() config = self.config time = self.flowsheet().config.time ######################################################################## # Add control volumes # ######################################################################## hot_side = _make_heater_control_volume( self, config.hot_side_name, config.hot_side_config, dynamic=config.dynamic, has_holdup=config.has_holdup, ) cold_side = _make_heater_control_volume( self, config.cold_side_name, config.cold_side_config, dynamic=config.dynamic, has_holdup=config.has_holdup, ) # Add refernces to the hot side and cold side, so that we have solid # names to refere to internally. side_1 and side_2 also maintain # compatability with older models. Using add_object_reference keeps # these from showing up when you iterate through pyomo compoents in a # model, so only the user specified control volume names are "seen" if not hasattr(self, "side_1"): add_object_reference(self, "side_1", hot_side) if not hasattr(self, "side_2"): add_object_reference(self, "side_2", cold_side) if not hasattr(self, "hot_side"): add_object_reference(self, "hot_side", hot_side) if not hasattr(self, "cold_side"): add_object_reference(self, "cold_side", cold_side) ######################################################################## # Add variables # ######################################################################## # Use hot side units as basis s1_metadata = config.hot_side_config.property_package.get_metadata() f_units = s1_metadata.get_derived_units("flow_mole") cp_units = s1_metadata.get_derived_units("heat_capacity_mole") q_units = s1_metadata.get_derived_units("power") u_units = s1_metadata.get_derived_units("heat_transfer_coefficient") a_units = s1_metadata.get_derived_units("area") temp_units = s1_metadata.get_derived_units("temperature") self.overall_heat_transfer_coefficient = pyo.Var( time, domain=pyo.PositiveReals, initialize=100.0, doc="Overall heat transfer coefficient", units=u_units, ) self.area = pyo.Var( domain=pyo.PositiveReals, initialize=1000.0, doc="Heat exchange area", units=a_units, ) self.heat_duty = pyo.Reference(cold_side.heat) ######################################################################## # Add ports # ######################################################################## i1 = self.add_inlet_port(name=f"{config.hot_side_name}_inlet", block=hot_side, doc="Hot side inlet") i2 = self.add_inlet_port(name=f"{config.cold_side_name}_inlet", block=cold_side, doc="Cold side inlet") o1 = self.add_outlet_port(name=f"{config.hot_side_name}_outlet", block=hot_side, doc="Hot side outlet") o2 = self.add_outlet_port(name=f"{config.cold_side_name}_outlet", block=cold_side, doc="Cold side outlet") # Using Andrew's function for now. I want these port names for backward # compatablity, but I don't want them to appear if you iterate throught # components and add_object_reference hides them from Pyomo. if not hasattr(self, "inlet_1"): add_object_reference(self, "inlet_1", i1) if not hasattr(self, "inlet_2"): add_object_reference(self, "inlet_2", i2) if not hasattr(self, "outlet_1"): add_object_reference(self, "outlet_1", o1) if not hasattr(self, "outlet_2"): add_object_reference(self, "outlet_2", o2) if not hasattr(self, "hot_inlet"): add_object_reference(self, "hot_inlet", i1) if not hasattr(self, "cold_inlet"): add_object_reference(self, "cold_inlet", i2) if not hasattr(self, "hot_outlet"): add_object_reference(self, "hot_outlet", o1) if not hasattr(self, "cold_outlet"): add_object_reference(self, "cold_outlet", o2) ######################################################################## # Add a unit level energy balance # ######################################################################## @self.Constraint(time, doc="Heat balance equation") def unit_heat_balance(b, t): return 0 == (hot_side.heat[t] + pyunits.convert(cold_side.heat[t], to_units=q_units)) ######################################################################## # Add some useful expressions for condenser performance # ######################################################################## @self.Expression(time, doc="Inlet temperature difference") def delta_temperature_in(b, t): return (hot_side.properties_in[t].temperature - pyunits.convert( cold_side.properties_in[t].temperature, temp_units)) @self.Expression(time, doc="Outlet temperature difference") def delta_temperature_out(b, t): return (hot_side.properties_out[t].temperature - pyunits.convert( cold_side.properties_out[t].temperature, temp_units)) @self.Expression(time, doc="NTU Based temperature difference") def delta_temperature_ntu(b, t): return (hot_side.properties_in[t].temperature_sat - pyunits.convert(cold_side.properties_in[t].temperature, temp_units)) @self.Expression( time, doc="Minimum product of flow rate and heat " "capacity (always on tube side since shell side has phase change)") def mcp_min(b, t): return pyunits.convert( cold_side.properties_in[t].flow_mol * cold_side.properties_in[t].cp_mol_phase['Liq'], f_units * cp_units) @self.Expression(time, doc="Number of transfer units (NTU)") def ntu(b, t): return b.overall_heat_transfer_coefficient[t] * b.area / b.mcp_min[ t] @self.Expression(time, doc="Condenser effectiveness factor") def effectiveness(b, t): return 1 - pyo.exp(-self.ntu[t]) @self.Expression(time, doc="Heat treansfer") def heat_transfer(b, t): return b.effectiveness[t] * b.mcp_min[t] * b.delta_temperature_ntu[ t] ######################################################################## # Add Equations to calculate heat duty based on NTU method # ######################################################################## @self.Constraint(time, doc="Heat transfer rate equation based on NTU method") def heat_transfer_equation(b, t): return (pyunits.convert(cold_side.heat[t], q_units) == self.heat_transfer[t]) @self.Constraint( time, doc="Shell side outlet enthalpy is saturated water enthalpy") def saturation_eqn(b, t): return (hot_side.properties_out[t].enth_mol == hot_side.properties_in[t].enth_mol_sat_phase["Liq"])
def build(self): """ Building model Args: None Returns: None """ ######################################################################## # Call UnitModel.build to setup dynamics and configure # ######################################################################## super().build() self._process_config() config = self.config ######################################################################## # Add variables # ######################################################################## # Use side 1 units as basis s1_metadata = config.hot_side_config.property_package.get_metadata() q_units = s1_metadata.get_derived_units("power") u_units = s1_metadata.get_derived_units("heat_transfer_coefficient") a_units = s1_metadata.get_derived_units("area") t_units = s1_metadata.get_derived_units("temperature") u = self.overall_heat_transfer_coefficient = Var( self.flowsheet().config.time, domain=PositiveReals, initialize=100.0, doc="Overall heat transfer coefficient", units=u_units) a = self.area = Var(domain=PositiveReals, initialize=1000.0, doc="Heat exchange area", units=a_units) self.delta_temperature_in = Var( self.flowsheet().config.time, initialize=10.0, doc="Temperature difference at the hot inlet end", units=t_units) self.delta_temperature_out = Var( self.flowsheet().config.time, initialize=10.1, doc="Temperature difference at the hot outlet end", units=t_units) if self.config.flow_pattern == HeatExchangerFlowPattern.crossflow: self.crossflow_factor = Var( self.flowsheet().config.time, initialize=1.0, doc="Factor to adjust coutercurrent flow heat " "transfer calculation for cross flow.", ) f = self.crossflow_factor ######################################################################## # Add control volumes # ######################################################################## _make_heater_control_volume( self, "side_1", config.hot_side_config, dynamic=config.dynamic, has_holdup=config.has_holdup, ) _make_heater_control_volume( self, "side_2", config.cold_side_config, dynamic=config.dynamic, has_holdup=config.has_holdup, ) # Add named references to side_1 and side_2, side 1 and 2 maintain # backward compatability and are names the user doesn't need to worry # about. The sign convention for duty is heat from side 1 to side 2 is # positive add_object_reference(self, config.hot_side_name, self.side_1) add_object_reference(self, config.cold_side_name, self.side_2) # Add convienient references to heat duty. self.heat_duty = Reference(self.side_2.heat) # Need to do this as Reference does not have units (Pyomo fixing this) q = self.side_2.heat ######################################################################## # Add ports # ######################################################################## # Keep old port names, just for backward compatability self.add_inlet_port(name="inlet_1", block=self.side_1, doc="Hot side inlet") self.add_inlet_port(name="inlet_2", block=self.side_2, doc="Cold side inlet") self.add_outlet_port(name="outlet_1", block=self.side_1, doc="Hot side outlet") self.add_outlet_port(name="outlet_2", block=self.side_2, doc="Cold side outlet") # Using Andrew's function for now, I think Pyomo's refrence has trouble # with scalar (pyomo) components. add_object_reference(self, config.hot_side_name + "_inlet", self.inlet_1) add_object_reference(self, config.cold_side_name + "_inlet", self.inlet_2) add_object_reference(self, config.hot_side_name + "_outlet", self.outlet_1) add_object_reference(self, config.cold_side_name + "_outlet", self.outlet_2) ######################################################################## # Add end temperature differnece constraints # ######################################################################## @self.Constraint(self.flowsheet().config.time) def delta_temperature_in_equation(b, t): if b.config.flow_pattern == HeatExchangerFlowPattern.cocurrent: return (b.delta_temperature_in[t] == b.side_1.properties_in[t].temperature - pyunits.convert(b.side_2.properties_in[t].temperature, to_units=t_units)) else: return (b.delta_temperature_in[t] == b.side_1.properties_in[t].temperature - pyunits.convert(b.side_2.properties_out[t].temperature, to_units=t_units)) @self.Constraint(self.flowsheet().config.time) def delta_temperature_out_equation(b, t): if b.config.flow_pattern == HeatExchangerFlowPattern.cocurrent: return (b.delta_temperature_out[t] == b.side_1.properties_out[t].temperature - pyunits.convert(b.side_2.properties_out[t].temperature, to_units=t_units)) else: return (b.delta_temperature_out[t] == b.side_1.properties_out[t].temperature - pyunits.convert(b.side_2.properties_in[t].temperature, to_units=t_units)) ######################################################################## # Add a unit level energy balance # ######################################################################## @self.Constraint(self.flowsheet().config.time) def unit_heat_balance(b, t): return 0 == ( self.side_1.heat[t] + pyunits.convert(self.side_2.heat[t], to_units=q_units)) ######################################################################## # Add delta T calculations using callack function, lots of options, # # and users can provide their own if needed # ######################################################################## config.delta_temperature_callback(self) ######################################################################## # Add Heat transfer equation # ######################################################################## deltaT = self.delta_temperature @self.Constraint(self.flowsheet().config.time) def heat_transfer_equation(b, t): if self.config.flow_pattern == HeatExchangerFlowPattern.crossflow: return pyunits.convert( q[t], to_units=q_units) == (f[t] * u[t] * a * deltaT[t]) else: return pyunits.convert(q[t], to_units=q_units) == (u[t] * a * deltaT[t]) ######################################################################## # Add symbols for LaTeX equation rendering # ######################################################################## self.overall_heat_transfer_coefficient.latex_symbol = "U" self.area.latex_symbol = "A" self.side_1.heat.latex_symbol = "Q_1" self.side_2.heat.latex_symbol = "Q_2" self.delta_temperature.latex_symbol = "\\Delta T"