def build_valve_liquid(): m = ConcreteModel() m.fs = FlowsheetBlock(default={"dynamic": False}) m.fs.properties = iapws95_ph.Iapws95ParameterBlock() m.fs.valve = SteamValve(default={"property_package": m.fs.properties, "phase": "Liq"}) return m
def build_valve_vapor(): m = ConcreteModel() m.fs = FlowsheetBlock(default={"dynamic": False}) m.fs.properties = iapws95.Iapws95ParameterBlock() m.fs.valve = SteamValve(default={"property_package": m.fs.properties}) return m
def build(self): super(TurbineMultistageData, self).build() config = self.config unit_cfg = { # general unit model config "dynamic":config.dynamic, "has_holdup":config.has_holdup, "has_phase_equilibrium":config.has_phase_equilibrium, "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 = RangeSet(ni) # Adding unit models #------------------------ # Splitter to inlet that splits main flow into parallel flows for # paritial arc admission to the turbine self.inlet_split = Separator(default=self._split_cfg(unit_cfg, ni)) self.throttle_valve = SteamValve(inlet_idx, default=unit_cfg) self.inlet_stage = TurbineInletStage(inlet_idx, default=unit_cfg) # mixer to combine the parallel flows back together self.inlet_mix = Mixer(default=self._mix_cfg(unit_cfg, ni)) # add turbine sections. # inlet stage -> hp stages -> ip stages -> lp stages -> outlet stage self.hp_stages = TurbineStage(RangeSet(config.num_hp), default=unit_cfg) self.ip_stages = TurbineStage(RangeSet(config.num_ip), default=unit_cfg) self.lp_stages = TurbineStage(RangeSet(config.num_lp), default=unit_cfg) self.outlet_stage = TurbineOutletStage(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 = Separator(config.hp_split_locations, default=s_sfg_default, initialize=hp_splt_cfg) if config.ip_split_locations: self.ip_split = Separator(config.ip_split_locations, default=s_sfg_default, initialize=ip_splt_cfg) if config.lp_split_locations: self.lp_split = Separator(config.lp_split_locations, default=s_sfg_default, initialize=lp_splt_cfg) # 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.split_to_valve_stream = Arc(inlet_idx, rule=_split_to_rule) self.valve_to_inlet_stage_stream = Arc(inlet_idx, rule=_valve_to_rule) self.inlet_stage_to_mix = 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: return { "source": turbines[i].outlet, "destination": splitters[i].inlet } elif j == 2: return { "source": splitters[i].outlet_1, "destination": turbines[i + 1].inlet } else: 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 = Set(initialize=self.hp_stages.index_set() * [1, 2]) self.ip_stream_idx = Set(initialize=self.ip_stages.index_set() * [1, 2]) self.lp_stream_idx = Set(initialize=self.lp_stages.index_set() * [1, 2]) # Throw out unneeded streams _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: 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.Expression(self.flowsheet().config.time) def power(b, t): return (sum(b.inlet_stage[i].power_shaft[t] for i in b.inlet_stage) + b.outlet_stage.power_shaft[t] + sum(b.hp_stages[i].power_shaft[t] for i in b.hp_stages) + sum(b.ip_stages[i].power_shaft[t] for i in b.ip_stages) + sum(b.lp_stages[i].power_shaft[t] for i in b.lp_stages)) # Connect inlet stage to hp section # not allowing disconnection of inlet and first regular hp stage 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) TransformationFactory("network.expand_arcs").apply_to(self)
def create_model(steady_state=True, time_set=[0, 3], nfe=5, calc_integ=True): """ Create a test model and solver Args: steady_state (bool): If True, create a steady state model, otherwise create a dynamic model time_set (list): The begining and end point of the time domain nfe (int): Number of finite elements argument for the DAE transformation calc_integ (bool): If Ture, calculate in the initial condition for the integral term, else use a fixed variable (fs.ctrl.err_i0), flase is the better option if you have a value from a previous time period Returns (tuple): (ConcreteModel, Solver) """ if steady_state: fs_cfg = {"dynamic": False} model_name = "Steam Tank, Steady State" else: fs_cfg = {"dynamic": True, "time_set": time_set} model_name = "Steam Tank, Dynamic" m = pyo.ConcreteModel(name=model_name) m.fs = FlowsheetBlock(default=fs_cfg) # Create a property parameter block m.fs.prop_water = iapws95.Iapws95ParameterBlock( default={"phase_presentation": iapws95.PhaseType.LG}) # Create the valve and tank models m.fs.valve_1 = SteamValve( default={ "dynamic": False, "has_holdup": False, "material_balance_type": MaterialBalanceType.componentTotal, "property_package": m.fs.prop_water }) m.fs.tank = Heater( default={ "has_holdup": True, "material_balance_type": MaterialBalanceType.componentTotal, "property_package": m.fs.prop_water }) m.fs.valve_2 = SteamValve( default={ "dynamic": False, "has_holdup": False, "material_balance_type": MaterialBalanceType.componentTotal, "property_package": m.fs.prop_water }) # Connect the models m.fs.v1_to_t = Arc(source=m.fs.valve_1.outlet, destination=m.fs.tank.inlet) m.fs.t_to_v2 = Arc(source=m.fs.tank.outlet, destination=m.fs.valve_2.inlet) # The control volume block doesn't assume the two phases are in equilibrium # by default, so I'll make that assumption here, I don't actually expect # liquid to form but who knows. The phase_fraction in the control volume is # volumetric phase fraction hense the densities. @m.fs.tank.Constraint(m.fs.time) def vol_frac_vap(b, t): return b.control_volume.properties_out[t].phase_frac["Vap"]\ *b.control_volume.properties_out[t].dens_mol\ /b.control_volume.properties_out[t].dens_mol_phase["Vap"]\ == b.control_volume.phase_fraction[t, "Vap"] # Add the stream constraints and do the DAE transformation pyo.TransformationFactory('network.expand_arcs').apply_to(m.fs) if not steady_state: pyo.TransformationFactory('dae.finite_difference').apply_to( m.fs, nfe=nfe, wrt=m.fs.time, scheme='BACKWARD') # Fix the derivative variables to zero at time 0 (steady state assumption) m.fs.fix_initial_conditions() # A tank pressure reference that's directly time-indexed m.fs.tank_pressure = pyo.Reference( m.fs.tank.control_volume.properties_out[:].pressure) # Add a controller m.fs.ctrl = PIDBlock( default={ "pv": m.fs.tank_pressure, "output": m.fs.valve_1.valve_opening, "upper": 1.0, "lower": 0.0, "calculate_initial_integral": calc_integ }) m.fs.ctrl.deactivate() # Don't want controller turned on by default # Fix the input variables m.fs.valve_1.inlet.enth_mol.fix(50000) m.fs.valve_1.inlet.pressure.fix(5e5) m.fs.valve_2.outlet.pressure.fix(101325) m.fs.valve_1.Cv.fix(0.001) m.fs.valve_2.Cv.fix(0.001) m.fs.valve_1.valve_opening.fix(1) m.fs.valve_2.valve_opening.fix(1) m.fs.tank.heat_duty.fix(0) m.fs.tank.control_volume.volume.fix(2.0) m.fs.ctrl.gain.fix(1e-6) m.fs.ctrl.time_i.fix(0.1) m.fs.ctrl.time_d.fix(0.1) m.fs.ctrl.setpoint.fix(3e5) # Initialize the model solver = pyo.SolverFactory("ipopt") solver.options = {'tol': 1e-6, 'linear_solver': "mumps", 'max_iter': 100} for t in m.fs.time: m.fs.valve_1.inlet.flow_mol = 100 # initial guess on flow # simple initialize m.fs.valve_1.initialize(outlvl=1) _set_port(m.fs.tank.inlet, m.fs.valve_1.outlet) m.fs.tank.initialize(outlvl=1) _set_port(m.fs.valve_2.inlet, m.fs.tank.outlet) m.fs.valve_2.initialize(outlvl=1) solver.solve(m, tee=True) # Return the model and solver return m, solver