def test_splitter(): m = pyo.ConcreteModel() m.fs = idaes.core.FlowsheetBlock(default={"dynamic": False}) m.fs.properties = iapws95.Iapws95ParameterBlock() m.fs.unit = HelmSplitter(default={ "property_package": m.fs.properties, "outlet_list": ["o1", "o2", "o3"] }) Fin = 1e4 # mol/s hin = 4000 # J/mol Pin = 101325 # Pa m.fs.unit.inlet.flow_mol[0].fix(Fin) m.fs.unit.inlet.enth_mol[0].fix(hin) m.fs.unit.inlet.pressure[0].fix(Pin) m.fs.unit.split_fraction[0, "o2"] = 0.3 m.fs.unit.split_fraction[0, "o3"] = 0.2 m.fs.unit.initialize() assert pyo.value(m.fs.unit.o1.flow_mol[0]) == pytest.approx(1e4 * 0.5, rel=1e-7) assert pyo.value(m.fs.unit.o2.flow_mol[0]) == pytest.approx(1e4 * 0.3, rel=1e-7) assert pyo.value(m.fs.unit.o3.flow_mol[0]) == pytest.approx(1e4 * 0.2, rel=1e-7) assert pyo.value(m.fs.unit.o1.pressure[0]) == pytest.approx(101325, rel=1e-7) assert pyo.value(m.fs.unit.o2.pressure[0]) == pytest.approx(101325, rel=1e-7) assert pyo.value(m.fs.unit.o3.pressure[0]) == pytest.approx(101325, rel=1e-7) assert pyo.value(m.fs.unit.o1.enth_mol[0]) == pytest.approx(4000, rel=1e-7) assert pyo.value(m.fs.unit.o2.enth_mol[0]) == pytest.approx(4000, rel=1e-7) assert pyo.value(m.fs.unit.o3.enth_mol[0]) == pytest.approx(4000, rel=1e-7)
def build(self): super().build() config = self.config unit_cfg = { # general unit model config "dynamic": config.dynamic, "has_holdup": config.has_holdup, "property_package": config.property_package, "property_package_args": config.property_package_args, } ni = self.config.num_parallel_inlet_stages inlet_idx = self.inlet_stage_idx = pyo.RangeSet(ni) thrtl_cfg = unit_cfg.copy() thrtl_cfg["valve_function"] = self.config.throttle_valve_function thrtl_cfg["valve_function_callback"] = \ self.config.throttle_valve_function_callback # Adding unit models # ------------------------ # Splitter to inlet that splits main flow into parallel flows for # paritial arc admission to the turbine self.inlet_split = HelmSplitter(default=self._split_cfg(unit_cfg, ni)) self.throttle_valve = SteamValve(inlet_idx, default=thrtl_cfg) self.inlet_stage = HelmTurbineInletStage(inlet_idx, default=unit_cfg) # mixer to combine the parallel flows back together self.inlet_mix = HelmMixer(default=self._mix_cfg(unit_cfg, ni)) # add turbine sections. # inlet stage -> hp stages -> ip stages -> lp stages -> outlet stage self.hp_stages = HelmTurbineStage(pyo.RangeSet(config.num_hp), default=unit_cfg) self.ip_stages = HelmTurbineStage(pyo.RangeSet(config.num_ip), default=unit_cfg) self.lp_stages = HelmTurbineStage(pyo.RangeSet(config.num_lp), default=unit_cfg) self.outlet_stage = HelmTurbineOutletStage(default=unit_cfg) for i in self.hp_stages: self.hp_stages[i].ratioP.fix() self.hp_stages[i].efficiency_isentropic.fix() for i in self.ip_stages: self.ip_stages[i].ratioP.fix() self.ip_stages[i].efficiency_isentropic.fix() for i in self.lp_stages: self.lp_stages[i].ratioP.fix() self.lp_stages[i].efficiency_isentropic.fix() # Then make splitter config. If number of outlets is specified # make a specific config, otherwise use default with 2 outlets s_sfg_default = self._split_cfg(unit_cfg, 2) hp_splt_cfg = {} ip_splt_cfg = {} lp_splt_cfg = {} # Now to finish up if there are more than two outlets, set that for i, v in config.hp_split_num_outlets.items(): hp_splt_cfg[i] = self._split_cfg(unit_cfg, v) for i, v in config.ip_split_num_outlets.items(): ip_splt_cfg[i] = self._split_cfg(unit_cfg, v) for i, v in config.lp_split_num_outlets.items(): lp_splt_cfg[i] = self._split_cfg(unit_cfg, v) # put in splitters for turbine steam extractions if config.hp_split_locations: self.hp_split = HelmSplitter(config.hp_split_locations, default=s_sfg_default, initialize=hp_splt_cfg) else: self.hp_split = {} if config.ip_split_locations: self.ip_split = HelmSplitter(config.ip_split_locations, default=s_sfg_default, initialize=ip_splt_cfg) else: self.ip_split = {} if config.lp_split_locations: self.lp_split = HelmSplitter(config.lp_split_locations, default=s_sfg_default, initialize=lp_splt_cfg) else: self.lp_split = {} # Done with unit models. Adding Arcs (streams). # ------------------------------------------------ # First up add streams in the inlet section def _split_to_rule(b, i): return { "source": getattr(self.inlet_split, "outlet_{}".format(i)), "destination": self.throttle_valve[i].inlet, } def _valve_to_rule(b, i): return { "source": self.throttle_valve[i].outlet, "destination": self.inlet_stage[i].inlet, } def _inlet_to_rule(b, i): return { "source": self.inlet_stage[i].outlet, "destination": getattr(self.inlet_mix, "inlet_{}".format(i)), } self.stream_throttle_inlet = Arc(inlet_idx, rule=_split_to_rule) self.stream_throttle_outlet = Arc(inlet_idx, rule=_valve_to_rule) self.stream_inlet_mix_inlet = Arc(inlet_idx, rule=_inlet_to_rule) # There are three sections HP, IP, and LP which all have the same sort # of internal connctions, so the functions below provide some generic # capcbilities for adding the internal Arcs (streams). def _arc_indexes(nstages, index_set, discon, splits): """ This takes the index set of all possible streams in a turbine section and throws out arc indexes for stages that are disconnected and arc indexes that are not needed because there is no splitter after a stage. Args: nstages (int): Number of stages in section index_set (Set): Index set for arcs in the section discon (list): Disconnected stages in the section splits (list): Spliter locations """ sr = set() # set of things to remove from the Arc index set for i in index_set: if (i[0] in discon or i[0] == nstages) and i[0] in splits: # don't connect stage i to next remove stream after split sr.add((i[0], 2)) elif (i[0] in discon or i[0] == nstages) and i[0] not in splits: # no splitter and disconnect so remove both streams sr.add((i[0], 1)) sr.add((i[0], 2)) elif i[0] not in splits: # no splitter and not disconnected so just second stream sr.add((i[0], 2)) else: # has splitter so need both streams don't remove anything pass for i in sr: # remove the unneeded Arc indexes index_set.remove(i) def _arc_rule(turbines, splitters): """ This creates a rule function for arcs in a turbine section. When this is used, the indexes for nonexistant stream will have already been removed, so any indexes the rule will get should have a stream associated. Args: turbines (TurbineStage): Indexed block with turbine section stages splitters (Separator): Indexed block of splitters """ def _rule(b, i, j): if i in splitters and j == 1: # stage to splitter return { "source": turbines[i].outlet, "destination": splitters[i].inlet, } elif j == 2: # splitter to next stage return { "source": splitters[i].outlet_1, "destination": turbines[i + 1].inlet, } else: # no splitter, stage to next stage return { "source": turbines[i].outlet, "destination": turbines[i + 1].inlet, } return _rule # Create initial arcs index sets with all possible streams self.hp_stream_idx = pyo.Set(initialize=self.hp_stages.index_set() * [1, 2]) self.ip_stream_idx = pyo.Set(initialize=self.ip_stages.index_set() * [1, 2]) self.lp_stream_idx = pyo.Set(initialize=self.lp_stages.index_set() * [1, 2]) # Throw out unneeded streams for disconnected stages or no splitter _arc_indexes( config.num_hp, self.hp_stream_idx, config.hp_disconnect, config.hp_split_locations, ) _arc_indexes( config.num_ip, self.ip_stream_idx, config.ip_disconnect, config.ip_split_locations, ) _arc_indexes( config.num_lp, self.lp_stream_idx, config.lp_disconnect, config.lp_split_locations, ) # Create connections internal to each turbine section (hp, ip, and lp) self.hp_stream = Arc(self.hp_stream_idx, rule=_arc_rule(self.hp_stages, self.hp_split)) self.ip_stream = Arc(self.ip_stream_idx, rule=_arc_rule(self.ip_stages, self.ip_split)) self.lp_stream = Arc(self.lp_stream_idx, rule=_arc_rule(self.lp_stages, self.lp_split)) # Connect hp section to ip section unless its a disconnect location last_hp = config.num_hp if 0 not in config.ip_disconnect and last_hp not in config.hp_disconnect: # Not disconnected stage so add stream, depending on splitter existance if last_hp in config.hp_split_locations: # connect splitter to ip self.hp_to_ip_stream = Arc( source=self.hp_split[last_hp].outlet_1, destination=self.ip_stages[1].inlet, ) else: # connect last hp to ip self.hp_to_ip_stream = Arc( source=self.hp_stages[last_hp].outlet, destination=self.ip_stages[1].inlet, ) # Connect ip section to lp section unless its a disconnect location last_ip = config.num_ip if 0 not in config.lp_disconnect and last_ip not in config.ip_disconnect: if last_ip in config.ip_split_locations: # connect splitter to ip self.ip_to_lp_stream = Arc( source=self.ip_split[last_ip].outlet_1, destination=self.lp_stages[1].inlet, ) else: # connect last hp to ip self.ip_to_lp_stream = Arc( source=self.ip_stages[last_ip].outlet, destination=self.lp_stages[1].inlet, ) # Connect inlet stage to hp section # not allowing disconnection of inlet and first regular hp stage if 0 in config.hp_split_locations: # connect inlet mix to splitter and splitter to hp section self.inlet_to_splitter_stream = Arc( source=self.inlet_mix.outlet, destination=self.hp_split[0].inlet) self.splitter_to_hp_stream = Arc( source=self.hp_split[0].outlet_1, destination=self.hp_stages[1].inlet) else: # connect mixer to first hp turbine stage self.inlet_to_hp_stream = Arc(source=self.inlet_mix.outlet, destination=self.hp_stages[1].inlet) self.power = pyo.Var(self.flowsheet().time, initialize=-1e8, doc="power (W)") @self.Constraint(self.flowsheet().time) def power_eqn(b, t): return (b.power[t] == b.outlet_stage.control_volume.work[t] * b.outlet_stage.efficiency_mech + sum(b.inlet_stage[i].control_volume.work[t] * b.inlet_stage[i].efficiency_mech for i in b.inlet_stage) + sum(b.hp_stages[i].control_volume.work[t] * b.hp_stages[i].efficiency_mech for i in b.hp_stages) + sum(b.ip_stages[i].control_volume.work[t] * b.ip_stages[i].efficiency_mech for i in b.ip_stages) + sum(b.lp_stages[i].control_volume.work[t] * b.lp_stages[i].efficiency_mech for i in b.lp_stages)) # Connect lp section to outlet stage, not allowing outlet stage to be # disconnected last_lp = config.num_lp if last_lp in config.lp_split_locations: # connect splitter to outlet self.lp_to_outlet_stream = Arc( source=self.lp_split[last_lp].outlet_1, destination=self.outlet_stage.inlet, ) else: # connect last lpstage to outlet self.lp_to_outlet_stream = Arc( source=self.lp_stages[last_lp].outlet, destination=self.outlet_stage.inlet, ) pyo.TransformationFactory("network.expand_arcs").apply_to(self)
class HelmTurbineMultistageData(UnitModelBlockData): CONFIG = ConfigBlock() _define_turbine_multistage_config(CONFIG) def build(self): super().build() config = self.config unit_cfg = { # general unit model config "dynamic": config.dynamic, "has_holdup": config.has_holdup, "property_package": config.property_package, "property_package_args": config.property_package_args, } ni = self.config.num_parallel_inlet_stages inlet_idx = self.inlet_stage_idx = pyo.RangeSet(ni) thrtl_cfg = unit_cfg.copy() thrtl_cfg["valve_function"] = self.config.throttle_valve_function thrtl_cfg["valve_function_callback"] = \ self.config.throttle_valve_function_callback # Adding unit models # ------------------------ # Splitter to inlet that splits main flow into parallel flows for # paritial arc admission to the turbine self.inlet_split = HelmSplitter(default=self._split_cfg(unit_cfg, ni)) self.throttle_valve = SteamValve(inlet_idx, default=thrtl_cfg) self.inlet_stage = HelmTurbineInletStage(inlet_idx, default=unit_cfg) # mixer to combine the parallel flows back together self.inlet_mix = HelmMixer(default=self._mix_cfg(unit_cfg, ni)) # add turbine sections. # inlet stage -> hp stages -> ip stages -> lp stages -> outlet stage self.hp_stages = HelmTurbineStage(pyo.RangeSet(config.num_hp), default=unit_cfg) self.ip_stages = HelmTurbineStage(pyo.RangeSet(config.num_ip), default=unit_cfg) self.lp_stages = HelmTurbineStage(pyo.RangeSet(config.num_lp), default=unit_cfg) self.outlet_stage = HelmTurbineOutletStage(default=unit_cfg) for i in self.hp_stages: self.hp_stages[i].ratioP.fix() self.hp_stages[i].efficiency_isentropic.fix() for i in self.ip_stages: self.ip_stages[i].ratioP.fix() self.ip_stages[i].efficiency_isentropic.fix() for i in self.lp_stages: self.lp_stages[i].ratioP.fix() self.lp_stages[i].efficiency_isentropic.fix() # Then make splitter config. If number of outlets is specified # make a specific config, otherwise use default with 2 outlets s_sfg_default = self._split_cfg(unit_cfg, 2) hp_splt_cfg = {} ip_splt_cfg = {} lp_splt_cfg = {} # Now to finish up if there are more than two outlets, set that for i, v in config.hp_split_num_outlets.items(): hp_splt_cfg[i] = self._split_cfg(unit_cfg, v) for i, v in config.ip_split_num_outlets.items(): ip_splt_cfg[i] = self._split_cfg(unit_cfg, v) for i, v in config.lp_split_num_outlets.items(): lp_splt_cfg[i] = self._split_cfg(unit_cfg, v) # put in splitters for turbine steam extractions if config.hp_split_locations: self.hp_split = HelmSplitter(config.hp_split_locations, default=s_sfg_default, initialize=hp_splt_cfg) else: self.hp_split = {} if config.ip_split_locations: self.ip_split = HelmSplitter(config.ip_split_locations, default=s_sfg_default, initialize=ip_splt_cfg) else: self.ip_split = {} if config.lp_split_locations: self.lp_split = HelmSplitter(config.lp_split_locations, default=s_sfg_default, initialize=lp_splt_cfg) else: self.lp_split = {} # Done with unit models. Adding Arcs (streams). # ------------------------------------------------ # First up add streams in the inlet section def _split_to_rule(b, i): return { "source": getattr(self.inlet_split, "outlet_{}".format(i)), "destination": self.throttle_valve[i].inlet, } def _valve_to_rule(b, i): return { "source": self.throttle_valve[i].outlet, "destination": self.inlet_stage[i].inlet, } def _inlet_to_rule(b, i): return { "source": self.inlet_stage[i].outlet, "destination": getattr(self.inlet_mix, "inlet_{}".format(i)), } self.stream_throttle_inlet = Arc(inlet_idx, rule=_split_to_rule) self.stream_throttle_outlet = Arc(inlet_idx, rule=_valve_to_rule) self.stream_inlet_mix_inlet = Arc(inlet_idx, rule=_inlet_to_rule) # There are three sections HP, IP, and LP which all have the same sort # of internal connctions, so the functions below provide some generic # capcbilities for adding the internal Arcs (streams). def _arc_indexes(nstages, index_set, discon, splits): """ This takes the index set of all possible streams in a turbine section and throws out arc indexes for stages that are disconnected and arc indexes that are not needed because there is no splitter after a stage. Args: nstages (int): Number of stages in section index_set (Set): Index set for arcs in the section discon (list): Disconnected stages in the section splits (list): Spliter locations """ sr = set() # set of things to remove from the Arc index set for i in index_set: if (i[0] in discon or i[0] == nstages) and i[0] in splits: # don't connect stage i to next remove stream after split sr.add((i[0], 2)) elif (i[0] in discon or i[0] == nstages) and i[0] not in splits: # no splitter and disconnect so remove both streams sr.add((i[0], 1)) sr.add((i[0], 2)) elif i[0] not in splits: # no splitter and not disconnected so just second stream sr.add((i[0], 2)) else: # has splitter so need both streams don't remove anything pass for i in sr: # remove the unneeded Arc indexes index_set.remove(i) def _arc_rule(turbines, splitters): """ This creates a rule function for arcs in a turbine section. When this is used, the indexes for nonexistant stream will have already been removed, so any indexes the rule will get should have a stream associated. Args: turbines (TurbineStage): Indexed block with turbine section stages splitters (Separator): Indexed block of splitters """ def _rule(b, i, j): if i in splitters and j == 1: # stage to splitter return { "source": turbines[i].outlet, "destination": splitters[i].inlet, } elif j == 2: # splitter to next stage return { "source": splitters[i].outlet_1, "destination": turbines[i + 1].inlet, } else: # no splitter, stage to next stage return { "source": turbines[i].outlet, "destination": turbines[i + 1].inlet, } return _rule # Create initial arcs index sets with all possible streams self.hp_stream_idx = pyo.Set(initialize=self.hp_stages.index_set() * [1, 2]) self.ip_stream_idx = pyo.Set(initialize=self.ip_stages.index_set() * [1, 2]) self.lp_stream_idx = pyo.Set(initialize=self.lp_stages.index_set() * [1, 2]) # Throw out unneeded streams for disconnected stages or no splitter _arc_indexes( config.num_hp, self.hp_stream_idx, config.hp_disconnect, config.hp_split_locations, ) _arc_indexes( config.num_ip, self.ip_stream_idx, config.ip_disconnect, config.ip_split_locations, ) _arc_indexes( config.num_lp, self.lp_stream_idx, config.lp_disconnect, config.lp_split_locations, ) # Create connections internal to each turbine section (hp, ip, and lp) self.hp_stream = Arc(self.hp_stream_idx, rule=_arc_rule(self.hp_stages, self.hp_split)) self.ip_stream = Arc(self.ip_stream_idx, rule=_arc_rule(self.ip_stages, self.ip_split)) self.lp_stream = Arc(self.lp_stream_idx, rule=_arc_rule(self.lp_stages, self.lp_split)) # Connect hp section to ip section unless its a disconnect location last_hp = config.num_hp if 0 not in config.ip_disconnect and last_hp not in config.hp_disconnect: # Not disconnected stage so add stream, depending on splitter existance if last_hp in config.hp_split_locations: # connect splitter to ip self.hp_to_ip_stream = Arc( source=self.hp_split[last_hp].outlet_1, destination=self.ip_stages[1].inlet, ) else: # connect last hp to ip self.hp_to_ip_stream = Arc( source=self.hp_stages[last_hp].outlet, destination=self.ip_stages[1].inlet, ) # Connect ip section to lp section unless its a disconnect location last_ip = config.num_ip if 0 not in config.lp_disconnect and last_ip not in config.ip_disconnect: if last_ip in config.ip_split_locations: # connect splitter to ip self.ip_to_lp_stream = Arc( source=self.ip_split[last_ip].outlet_1, destination=self.lp_stages[1].inlet, ) else: # connect last hp to ip self.ip_to_lp_stream = Arc( source=self.ip_stages[last_ip].outlet, destination=self.lp_stages[1].inlet, ) # Connect inlet stage to hp section # not allowing disconnection of inlet and first regular hp stage if 0 in config.hp_split_locations: # connect inlet mix to splitter and splitter to hp section self.inlet_to_splitter_stream = Arc( source=self.inlet_mix.outlet, destination=self.hp_split[0].inlet) self.splitter_to_hp_stream = Arc( source=self.hp_split[0].outlet_1, destination=self.hp_stages[1].inlet) else: # connect mixer to first hp turbine stage self.inlet_to_hp_stream = Arc(source=self.inlet_mix.outlet, destination=self.hp_stages[1].inlet) self.power = pyo.Var(self.flowsheet().time, initialize=-1e8, doc="power (W)") @self.Constraint(self.flowsheet().time) def power_eqn(b, t): return (b.power[t] == b.outlet_stage.control_volume.work[t] * b.outlet_stage.efficiency_mech + sum(b.inlet_stage[i].control_volume.work[t] * b.inlet_stage[i].efficiency_mech for i in b.inlet_stage) + sum(b.hp_stages[i].control_volume.work[t] * b.hp_stages[i].efficiency_mech for i in b.hp_stages) + sum(b.ip_stages[i].control_volume.work[t] * b.ip_stages[i].efficiency_mech for i in b.ip_stages) + sum(b.lp_stages[i].control_volume.work[t] * b.lp_stages[i].efficiency_mech for i in b.lp_stages)) # Connect lp section to outlet stage, not allowing outlet stage to be # disconnected last_lp = config.num_lp if last_lp in config.lp_split_locations: # connect splitter to outlet self.lp_to_outlet_stream = Arc( source=self.lp_split[last_lp].outlet_1, destination=self.outlet_stage.inlet, ) else: # connect last lpstage to outlet self.lp_to_outlet_stream = Arc( source=self.lp_stages[last_lp].outlet, destination=self.outlet_stage.inlet, ) pyo.TransformationFactory("network.expand_arcs").apply_to(self) def _split_cfg(self, unit_cfg, no=2): """ This creates a configuration dictionary for a splitter. Args: unit_cfg: The base unit config dict. no: Number of outlets, default=2 """ # Create a dict for splitter config args cfg = copy.copy(unit_cfg) cfg.update(num_outlets=no) return cfg def _mix_cfg(self, unit_cfg, ni=2): """ This creates a configuration dictionary for a mixer. Args: unit_cfg: The base unit config dict. ni: Number of inlets, default=2 """ cfg = copy.copy(unit_cfg) cfg.update( num_inlets=ni, momentum_mixing_type=MomentumMixingType.minimize_and_equality) return cfg def throttle_cv_fix(self, value): """ Fix the thottle valve coefficients. These are generally the same for each of the parallel stages so this provides a convenient way to set them. Args: value: The value to fix the turbine inlet flow coefficients at """ for i in self.throttle_valve: self.throttle_valve[i].Cv.fix(value) def turbine_inlet_cf_fix(self, value): """ Fix the inlet turbine stage flow coefficient. These are generally the same for each of the parallel stages so this provides a convenient way to set them. Args: value: The value to fix the turbine inlet flow coefficients at """ for i in self.inlet_stage: self.inlet_stage[i].flow_coeff.fix(value) def _init_section( self, stages, splits, disconnects, prev_port, outlvl, solver, optarg, copy_disconneted_flow, copy_disconneted_pressure, ): """ Reuse the initializtion for HP, IP and, LP sections. """ if 0 in splits: copy_port(splits[0].inlet, prev_port) splits[0].initialize(outlvl=outlvl, solver=solver, optarg=optarg) prev_port = splits[0].outlet_1 for i in stages: if i - 1 not in disconnects: copy_port(stages[i].inlet, prev_port) else: if copy_disconneted_flow: for t in stages[i].inlet.flow_mol: stages[i].inlet.flow_mol[t] = pyo.value( prev_port.flow_mol[t]) if copy_disconneted_pressure: for t in stages[i].inlet.pressure: stages[i].inlet.pressure[t] = pyo.value( prev_port.pressure[t]) stages[i].initialize(outlvl=outlvl, solver=solver, optarg=optarg) prev_port = stages[i].outlet if i in splits: copy_port(splits[i].inlet, prev_port) splits[i].initialize(outlvl=outlvl, solver=solver, optarg=optarg) prev_port = splits[i].outlet_1 return prev_port def turbine_outlet_cf_fix(self, value): """ Fix the inlet turbine stage flow coefficient. These are generally the same for each of the parallel stages so this provides a convenient way to set them. Args: value: The value to fix the turbine inlet flow coefficients at """ self.outlet_stage.flow_coeff.fix(value) def initialize(self, outlvl=idaeslog.NOTSET, solver=None, flow_iterate=2, optarg=None, copy_disconneted_flow=True, copy_disconneted_pressure=True, calculate_outlet_cf=False, calculate_inlet_cf=False): """ Initialize Args: outlvl: logging level default is NOTSET, which inherits from the parent logger solver: the NL solver flow_iterate: If not calculating flow coefficients, this is the number of times to update the flow and repeat initialization (1 to 5 where 1 does not update the flow guess) optarg: solver arguments, default is None copy_disconneted_flow: Copy the flow through the disconnected stages default is True copy_disconneted_pressure: Copy the pressure through the disconnected stages default is True calculate_outlet_cf: Use the flow initial flow guess to calculate the outlet stage flow coefficient, default is False, calculate_inlet_cf: Use the inlet stage ratioP to calculate the flow coefficent for the inlet stage default is False Returns: None """ # Setup loggers init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit") # Store initial model specs, restored at the end of initializtion, so # the problem is not altered. This can restore fixed/free vars, # active/inactive constraints, and fixed variable values. sp = StoreSpec.value_isfixed_isactive(only_fixed=True) istate = to_json(self, return_dict=True, wts=sp) # Assume the flow into the turbine is a reasonable guess for # initializtion flow_guess = self.inlet_split.inlet.flow_mol[0].value for it_count in range(flow_iterate): self.inlet_split.initialize(outlvl=outlvl, solver=solver, optarg=optarg) # Initialize valves for i in self.inlet_stage_idx: u = self.throttle_valve[i] copy_port(u.inlet, getattr(self.inlet_split, "outlet_{}".format(i))) u.initialize(outlvl=outlvl, solver=solver, optarg=optarg) # Initialize turbine for i in self.inlet_stage_idx: u = self.inlet_stage[i] copy_port(u.inlet, self.throttle_valve[i].outlet) u.initialize(outlvl=outlvl, solver=solver, optarg=optarg, calculate_cf=calculate_inlet_cf) # Initialize Mixer self.inlet_mix.use_minimum_inlet_pressure_constraint() for i in self.inlet_stage_idx: copy_port( getattr(self.inlet_mix, "inlet_{}".format(i)), self.inlet_stage[i].outlet, ) getattr(self.inlet_mix, "inlet_{}".format(i)).fix() self.inlet_mix.initialize(outlvl=outlvl, solver=solver, optarg=optarg) for i in self.inlet_stage_idx: getattr(self.inlet_mix, "inlet_{}".format(i)).unfix() self.inlet_mix.use_equal_pressure_constraint() prev_port = self.inlet_mix.outlet prev_port = self._init_section( self.hp_stages, self.hp_split, self.config.hp_disconnect, prev_port, outlvl, solver, optarg, copy_disconneted_flow=copy_disconneted_flow, copy_disconneted_pressure=copy_disconneted_pressure, ) if len(self.hp_stages) in self.config.hp_disconnect: self.config.ip_disconnect.append(0) prev_port = self._init_section( self.ip_stages, self.ip_split, self.config.ip_disconnect, prev_port, outlvl, solver, optarg, copy_disconneted_flow=copy_disconneted_flow, copy_disconneted_pressure=copy_disconneted_pressure, ) if len(self.ip_stages) in self.config.ip_disconnect: self.config.lp_disconnect.append(0) prev_port = self._init_section( self.lp_stages, self.lp_split, self.config.lp_disconnect, prev_port, outlvl, solver, optarg, copy_disconneted_flow=copy_disconneted_flow, copy_disconneted_pressure=copy_disconneted_pressure, ) copy_port(self.outlet_stage.inlet, prev_port) self.outlet_stage.initialize(outlvl=outlvl, solver=solver, optarg=optarg, calculate_cf=calculate_outlet_cf) if calculate_outlet_cf: break if it_count < flow_iterate - 1: for t in self.inlet_split.inlet.flow_mol: self.inlet_split.inlet.flow_mol[t].value = \ self.outlet_stage.inlet.flow_mol[t].value for s in self.hp_split.values(): for i, o in enumerate(s.outlet_list): if i == 0: continue o = getattr(s, o) self.inlet_split.inlet.flow_mol[t].value += \ o.flow_mol[t].value for s in self.ip_split.values(): for i, o in enumerate(s.outlet_list): if i == 0: continue o = getattr(s, o) self.inlet_split.inlet.flow_mol[t].value += \ o.flow_mol[t].value for s in self.lp_split.values(): for i, o in enumerate(s.outlet_list): if i == 0: continue o = getattr(s, o) self.inlet_split.inlet.flow_mol[t].value += \ o.flow_mol[t].value if calculate_inlet_cf: # cf was probably fixed, so will have to set the value agian here # if you ask for it to be calculated. icf = {} for i in self.inlet_stage: for t in self.inlet_stage[i].flow_coeff: icf[i, t] = pyo.value(self.inlet_stage[i].flow_coeff[t]) if calculate_outlet_cf: ocf = pyo.value(self.outlet_stage.flow_coeff) from_json(self, sd=istate, wts=sp) if calculate_inlet_cf: # cf was probably fixed, so will have to set the value agian here # if you ask for it to be calculated. for t in self.inlet_stage[i].flow_coeff: for i in self.inlet_stage: self.inlet_stage[i].flow_coeff[t] = icf[i, t] if calculate_outlet_cf: self.outlet_stage.flow_coeff = ocf def calculate_scaling_factors(self): super().calculate_scaling_factors() # Add a default power scale # pretty safe to say power is around 100 to 1000 MW for t in self.power: if iscale.get_scaling_factor(self.power[t]) is None: iscale.set_scaling_factor(self.power[t], 1e-8) for t, c in self.power_eqn.items(): power_scale = iscale.get_scaling_factor(self.power[t], default=1, warning=True) # Set power equation scale factor iscale.constraint_scaling_transform(c, power_scale, overwrite=False)
def add_unit_models(m): fs = m.fs_main.fs_blr prop_water = m.fs_main.prop_water prop_gas = m.fs_main.prop_gas fs.num_mills = pyo.Var() fs.num_mills.fix(4) # 14 waterwall zones fs.ww_zones = pyo.RangeSet(14) # boiler based on surrogate fs.aBoiler = BoilerSurrogate(default={"dynamic": False, "side_1_property_package": prop_gas, "side_2_property_package": prop_gas, "has_heat_transfer": False, "has_pressure_change": False, "has_holdup": False}) # model a drum by a WaterFlash, a Mixer and a Drum model fs.aFlash = WaterFlash(default={"dynamic": False, "property_package": prop_water, "has_phase_equilibrium": False, "has_heat_transfer": False, "has_pressure_change": False}) fs.aMixer = HelmMixer(default={"dynamic": False, "property_package": prop_water, "momentum_mixing_type": MomentumMixingType.equality, "inlet_list": ["FeedWater", "SatWater"]}) fs.aDrum = Drum1D(default={"property_package": prop_water, "has_holdup": True, "has_heat_transfer": True, "has_pressure_change": True, "finite_elements": 4, "inside_diameter": 1.778, "thickness": 0.127}) fs.blowdown_split = HelmSplitter( default={ "dynamic": False, "property_package": prop_water, "outlet_list": ["FW_Downcomer", "FW_Blowdown"], } ) # downcomer fs.aDowncomer = Downcomer(default={ "dynamic": False, "property_package": prop_water, "has_holdup": True, "has_heat_transfer": True, "has_pressure_change": True}) # 14 WaterwallSection units fs.Waterwalls = WaterwallSection(fs.ww_zones, default={ "has_holdup": True, "property_package": prop_water, "has_equilibrium_reactions": False, "has_heat_of_reaction": False, "has_heat_transfer": True, "has_pressure_change": True}) # roof superheater fs.aRoof = SteamHeater(default={ "dynamic": False, "property_package": prop_water, "has_holdup": True, "has_equilibrium_reactions": False, "has_heat_of_reaction": False, "has_heat_transfer": True, "has_pressure_change": True, "single_side_only" : True}) # platen superheater fs.aPlaten = SteamHeater(default={ "dynamic": False, "property_package": prop_water, "has_holdup": True, "has_equilibrium_reactions": False, "has_heat_of_reaction": False, "has_heat_transfer": True, "has_pressure_change": True, "single_side_only" : False}) # 1st reheater fs.aRH1 = HeatExchangerCrossFlow2D(default={ "tube_side":{"property_package": prop_water, "has_holdup": False, "has_pressure_change": True}, "shell_side":{"property_package": prop_gas, "has_holdup": False, "has_pressure_change": True}, "finite_elements": 4, "flow_type": "counter_current", "tube_arrangement": "in-line", "tube_side_water_phase": "Vap", "has_radiation": True, "radial_elements": 5, "inside_diameter": 2.202*0.0254, "thickness": 0.149*0.0254}) # 2nd reheater fs.aRH2 = HeatExchangerCrossFlow2D(default={ "tube_side":{"property_package": prop_water, "has_holdup": False, "has_pressure_change": True}, "shell_side":{"property_package": prop_gas, "has_holdup": False, "has_pressure_change": True}, "finite_elements": 2, "flow_type": "counter_current", "tube_arrangement": "in-line", "tube_side_water_phase": "Vap", "has_radiation": True, "radial_elements": 5, "inside_diameter": 2.217*0.0254, "thickness": 0.1415*0.0254}) # primary superheater fs.aPSH = HeatExchangerCrossFlow2D(default={ "tube_side":{"property_package": prop_water, "has_holdup": False, "has_pressure_change": True}, "shell_side":{"property_package": prop_gas, "has_holdup": False, "has_pressure_change": True}, "finite_elements": 5, "flow_type": "counter_current", "tube_arrangement": "in-line", "tube_side_water_phase": "Vap", "has_radiation": True, "radial_elements": 5, "inside_diameter": 1.45*0.0254, "thickness": 0.15*0.0254}) # economizer fs.aECON = HeatExchangerCrossFlow2D(default={ "tube_side":{"property_package": prop_water, "has_holdup": False, "has_pressure_change": True}, "shell_side":{"property_package": prop_gas, "has_holdup": False, "has_pressure_change": True}, "finite_elements": 5, "flow_type": "counter_current", "tube_arrangement": "in-line", "tube_side_water_phase": "Liq", "has_radiation": False, "radial_elements": 5, "inside_diameter": 1.452*0.0254, "thickness": 0.149*0.0254}) # water pipe from economizer outlet to drum fs.aPipe = WaterPipe(default={ "dynamic": False, "property_package": prop_water, "has_holdup": True, "has_heat_transfer": False, "has_pressure_change": True, "water_phase": 'Liq', "contraction_expansion_at_end": 'None'}) # a mixer to mix hot primary air with tempering air fs.Mixer_PA = Mixer( default={ "dynamic": False, "property_package": prop_gas, "momentum_mixing_type": MomentumMixingType.equality, "inlet_list": ["PA_inlet", "TA_inlet"], } ) # attemperator for main steam before platen SH fs.Attemp = HelmMixer( default={ "dynamic": False, "property_package": prop_water, "momentum_mixing_type": MomentumMixingType.equality, "inlet_list": ["Steam_inlet", "Water_inlet"], } ) # air preheater as three-stream heat exchanger with heat loss to ambient, # side_1: flue gas # side_2:PA (priamry air?) # side_3:PA (priamry air?) fs.aAPH = HeatExchangerWith3Streams( default={"dynamic": False, "side_1_property_package": prop_gas, "side_2_property_package": prop_gas, "side_3_property_package": prop_gas, "has_heat_transfer": True, "has_pressure_change": True, "has_holdup": False, "flow_type_side_2": "counter-current", "flow_type_side_3": "counter-current", } ) return m