def build_SepRO(m, base="TDS"): """ Builds RO model based on the IDAES separator. Requires prop_TDS property package. """ prop = property_models.get_prop(m, base=base) m.fs.RO = Separator( default={ "property_package": prop, "outlet_list": ["retentate", "permeate"], "split_basis": SplittingType.componentFlow, "energy_split_basis": EnergySplittingType.equal_temperature, } ) # specify if base == "TDS": m.fs.RO.split_fraction[0, "permeate", "H2O"].fix(0.5) m.fs.RO.split_fraction[0, "permeate", "TDS"].fix(0.01) else: raise ValueError( "Unexpected property base {base} provided to build_SepRO" "".format(base=base) ) # scale set_scaling_factor( m.fs.RO.split_fraction, 1 ) # TODO: IDAES should set these scaling factors by default constraint_scaling_transform(m.fs.RO.sum_split_frac[0.0, "H2O"], 1) constraint_scaling_transform(m.fs.RO.sum_split_frac[0.0, "TDS"], 1)
def build_SepNF(m, base="ion"): """ Builds NF model based on the IDAES separator for a specified property base. Requires prop_ion or prop_salt property package. """ prop = property_models.get_prop(m, base=base) m.fs.NF = Separator( default={ "property_package": prop, "outlet_list": ["retentate", "permeate"], "split_basis": SplittingType.componentFlow, "energy_split_basis": EnergySplittingType.equal_temperature, } ) # specify if base == "ion": m.fs.NF.split_fraction[0, "permeate", "H2O"].fix(0.9) m.fs.NF.split_fraction[0, "permeate", "Na"].fix(0.9) m.fs.NF.split_fraction[0, "permeate", "Ca"].fix(0.1) m.fs.NF.split_fraction[0, "permeate", "Mg"].fix(0.1) m.fs.NF.split_fraction[0, "permeate", "SO4"].fix(0.1) # Cl split fraction determined through electro-neutrality for the retentate charge_dict = {"Na": 1, "Ca": 2, "Mg": 2, "SO4": -2, "Cl": -1} m.fs.NF.EN_out = Constraint( expr=0 == sum( charge_dict[j] * m.fs.NF.retentate_state[0].flow_mol_phase_comp["Liq", j] for j in charge_dict ) ) constraint_scaling_transform(m.fs.NF.EN_out, 1) elif base == "salt": m.fs.NF.split_fraction[0, "permeate", "H2O"].fix(0.9) m.fs.NF.split_fraction[0, "permeate", "NaCl"].fix(0.9) m.fs.NF.split_fraction[0, "permeate", "CaSO4"].fix(0.1) m.fs.NF.split_fraction[0, "permeate", "MgSO4"].fix(0.1) m.fs.NF.split_fraction[0, "permeate", "MgCl2"].fix(0.2) # scale set_scaling_factor( m.fs.NF.split_fraction, 1 ) # TODO: IDAES should set these scaling factors by default if base == "ion": constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "H2O"], 1) constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "Na"], 1) constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "Ca"], 1) constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "Mg"], 1) constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "SO4"], 1) constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "Cl"], 1) elif base == "salt": constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "H2O"], 1) constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "NaCl"], 1) constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "CaSO4"], 1) constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "MgSO4"], 1) constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "MgCl2"], 1)
def declare_heatx_unit(h): """ Declare all units of the heat exchanger network. Optional keyword arguments: --------------------------- :param h: the heat exchanger network :return: return the heat exchanger network """ h.fs = FlowsheetBlock(default={"dynamic": False}) h.fs.thermo_params = thermo_props.MethaneParameterBlock() h.fs.Split2 = Separator(default={ "dynamic": False, "num_outlets": 3, "property_package": h.fs.thermo_params }) h.fs.HX1a = HeatExchanger( default={ "dynamic": False, "delta_temperature_callback": delta_temperature_amtd_callback, "shell": { "property_package": h.fs.thermo_params }, "tube": { "property_package": h.fs.thermo_params } }) h.fs.HX2a = HeatExchanger( default={ "dynamic": False, "delta_temperature_callback": delta_temperature_amtd_callback, "shell": { "property_package": h.fs.thermo_params }, "tube": { "property_package": h.fs.thermo_params } }) h.fs.HX2b = HeatExchanger( default={ "dynamic": False, "delta_temperature_callback": delta_temperature_amtd_callback, "shell": { "property_package": h.fs.thermo_params }, "tube": { "property_package": h.fs.thermo_params } }) h.fs.HX1b = Heater(default={"property_package": h.fs.thermo_params}) return h
def concrete_model_base(m): """ Concrete and declare all units and streams in the model. Optional keyword arguments: --------------------------- :param m: the model :return: return the model """ m.fs = FlowsheetBlock(default={"dynamic": False}) m.fs.thermo_params = thermo_props.MethaneParameterBlock() # Declare all Units m.fs.HX1 = Heater(default={"property_package": m.fs.thermo_params}) m.fs.HX2a = Heater(default={"property_package": m.fs.thermo_params}) m.fs.HX2b = Heater(default={"property_package": m.fs.thermo_params}) m.fs.Mix1 = Mixer(default={ "dynamic": False, "property_package": m.fs.thermo_params }) m.fs.Mix2 = Mixer(default={ "dynamic": False, "property_package": m.fs.thermo_params }) m.fs.Mix3 = Mixer(default={ "dynamic": False, "property_package": m.fs.thermo_params }) m.fs.Split1 = Separator( default={ "dynamic": False, "split_basis": SplittingType.componentFlow, "property_package": m.fs.thermo_params }) m.fs.Reformer = GibbsReactor( default={ "dynamic": False, "property_package": m.fs.thermo_params, "has_pressure_change": False, "has_heat_transfer": True }) m.fs.SOFC = GibbsReactor( default={ "dynamic": False, "property_package": m.fs.thermo_params, "has_pressure_change": False, "has_heat_transfer": True }) m.fs.Burner = GibbsReactor( default={ "dynamic": False, "property_package": m.fs.thermo_params, "has_pressure_change": False, "has_heat_transfer": True }) # Declare all Streams m.fs.stream0 = Arc(source=m.fs.Mix1.outlet, destination=m.fs.HX1.inlet) m.fs.stream1 = Arc(source=m.fs.Split1.outlet_1, destination=m.fs.HX2b.inlet) m.fs.stream2 = Arc(source=m.fs.HX1.outlet, destination=m.fs.Reformer.inlet) m.fs.stream3 = Arc(source=m.fs.Split1.outlet_2, destination=m.fs.HX2a.inlet) m.fs.stream4 = Arc(source=m.fs.Reformer.outlet, destination=m.fs.Mix2.inlet_1) m.fs.stream5 = Arc(source=m.fs.HX2b.outlet, destination=m.fs.Mix2.inlet_2) m.fs.stream6 = Arc(source=m.fs.Mix2.outlet, destination=m.fs.SOFC.inlet) m.fs.stream7 = Arc(source=m.fs.HX2a.outlet, destination=m.fs.Mix3.inlet_2) m.fs.stream8 = Arc(source=m.fs.SOFC.outlet, destination=m.fs.Mix3.inlet_1) m.fs.stream9 = Arc(source=m.fs.Mix3.outlet, destination=m.fs.Burner.inlet) TransformationFactory("network.expand_arcs").apply_to(m) return m
def build(): # flowsheet set up m = ConcreteModel() m.fs = FlowsheetBlock(default={'dynamic': False}) m.fs.properties = props.NaClParameterBlock() financials.add_costing_param_block(m.fs) # unit models m.fs.feed = Feed(default={'property_package': m.fs.properties}) m.fs.S1 = Separator(default={ "property_package": m.fs.properties, "outlet_list": ['P1', 'PXR'] }) m.fs.P1 = Pump(default={'property_package': m.fs.properties}) m.fs.PXR = PressureExchanger(default={'property_package': m.fs.properties}) m.fs.P2 = Pump(default={'property_package': m.fs.properties}) m.fs.M1 = Mixer( default={ "property_package": m.fs.properties, "momentum_mixing_type": MomentumMixingType.equality, # booster pump will match pressure "inlet_list": ['P1', 'P2'] }) m.fs.RO = ReverseOsmosis0D(default={ "property_package": m.fs.properties, "has_pressure_change": True }) m.fs.product = Product(default={'property_package': m.fs.properties}) m.fs.disposal = Product(default={'property_package': m.fs.properties}) # additional variables or expressions feed_flow_vol_total = m.fs.feed.properties[0].flow_vol product_flow_vol_total = m.fs.product.properties[0].flow_vol m.fs.recovery = Expression(expr=product_flow_vol_total / feed_flow_vol_total) m.fs.annual_water_production = Expression(expr=pyunits.convert( product_flow_vol_total, to_units=pyunits.m**3 / pyunits.year) * m.fs.costing_param.load_factor) pump_power_total = m.fs.P1.work_mechanical[0] + m.fs.P2.work_mechanical[0] m.fs.specific_energy_consumption = Expression( expr=pyunits.convert(pump_power_total, to_units=pyunits.kW) / pyunits.convert(product_flow_vol_total, to_units=pyunits.m**3 / pyunits.hr)) # costing m.fs.P1.get_costing(module=financials, pump_type="High pressure") m.fs.P2.get_costing(module=financials, pump_type="High pressure") m.fs.RO.get_costing(module=financials) m.fs.PXR.get_costing(module=financials) financials.get_system_costing(m.fs) # connections m.fs.s01 = Arc(source=m.fs.feed.outlet, destination=m.fs.S1.inlet) m.fs.s02 = Arc(source=m.fs.S1.P1, destination=m.fs.P1.inlet) m.fs.s03 = Arc(source=m.fs.P1.outlet, destination=m.fs.M1.P1) m.fs.s04 = Arc(source=m.fs.M1.outlet, destination=m.fs.RO.inlet) m.fs.s05 = Arc(source=m.fs.RO.permeate, destination=m.fs.product.inlet) m.fs.s06 = Arc(source=m.fs.RO.retentate, destination=m.fs.PXR.high_pressure_inlet) m.fs.s07 = Arc(source=m.fs.PXR.high_pressure_outlet, destination=m.fs.disposal.inlet) m.fs.s08 = Arc(source=m.fs.S1.PXR, destination=m.fs.PXR.low_pressure_inlet) m.fs.s09 = Arc(source=m.fs.PXR.low_pressure_outlet, destination=m.fs.P2.inlet) m.fs.s10 = Arc(source=m.fs.P2.outlet, destination=m.fs.M1.P2) TransformationFactory("network.expand_arcs").apply_to(m) # scaling m.fs.properties.set_default_scaling('flow_mass_phase_comp', 1, index=('Liq', 'H2O')) m.fs.properties.set_default_scaling('flow_mass_phase_comp', 1e2, index=('Liq', 'NaCl')) iscale.calculate_scaling_factors(m) return m
def build_pretreatment_NF(m, has_bypass=True, NF_type="ZO", NF_base="ion"): """ Builds NF pretreatment including specified feed and auxiliary equipment. Arguments: has_bypass: True or False, default = True NF_type: 'Sep' or 'ZO', default = 'ZO' NF_base: 'ion' or 'salt', default = 'ion' """ pretrt_port = {} prop = property_models.get_prop(m, base=NF_base) # build feed feed_block.build_feed(m, base=NF_base) # build NF if NF_type == "Sep": unit_separator.build_SepNF(m, base=NF_base) elif NF_type == "ZO": unit_ZONF.build_ZONF(m, base=NF_base) m.fs.pump_NF = Pump(default={"property_package": prop}) else: raise ValueError( "Unexpected model type {NF_type} provided to build_NF_no_bypass" "".format(NF_type=NF_type) ) if has_bypass: # build auxiliary units m.fs.splitter = Separator( default={ "property_package": prop, "outlet_list": ["pretreatment", "bypass"], "split_basis": SplittingType.totalFlow, "energy_split_basis": EnergySplittingType.equal_temperature, } ) m.fs.mixer = Mixer( default={"property_package": prop, "inlet_list": ["pretreatment", "bypass"]} ) # connect models m.fs.s_pretrt_feed_splitter = Arc( source=m.fs.feed.outlet, destination=m.fs.splitter.inlet ) m.fs.s_pretrt_splitter_mixer = Arc( source=m.fs.splitter.bypass, destination=m.fs.mixer.bypass ) if NF_type == "ZO": m.fs.s_pretrt_splitter_pumpNF = Arc( source=m.fs.splitter.pretreatment, destination=m.fs.pump_NF.inlet ) m.fs.s_pretrt_pumpNF_NF = Arc( source=m.fs.pump_NF.outlet, destination=m.fs.NF.inlet ) else: m.fs.s_pretrt_splitter_NF = Arc( source=m.fs.splitter.pretreatment, destination=m.fs.NF.inlet ) m.fs.s_pretrt_NF_mixer = Arc( source=m.fs.NF.permeate, destination=m.fs.mixer.pretreatment ) # specify (NF and feed is already specified, mixer has 0 DOF, splitter has 1 DOF, NF pump has 2 DOF) # splitter m.fs.splitter.split_fraction[0, "bypass"].fix(0.1) if NF_type == "ZO": m.fs.pump_NF.efficiency_pump.fix(0.80) m.fs.pump_NF.control_volume.properties_out[0].pressure.fix(4e5) # inlet/outlet ports for pretreatment pretrt_port["out"] = m.fs.mixer.outlet pretrt_port["waste"] = m.fs.NF.retentate else: # no bypass # build auxiliary units (none) # connect models if NF_type == "ZO": m.fs.s_pretrt_feed_pumpNF = Arc( source=m.fs.feed.outlet, destination=m.fs.pump_NF.inlet ) m.fs.s_pretrt_pumpNF_NF = Arc( source=m.fs.pump_NF.outlet, destination=m.fs.NF.inlet ) # TODO: should source be m.fs.pump_NF.outlet? Double-check here and other arcs with pump_NF else: m.fs.s_pretrt_feed_NF = Arc( source=m.fs.feed.outlet, destination=m.fs.NF.inlet ) # specify (NF and feed are already specified, NF pump has 2 DOF) if NF_type == "ZO": m.fs.pump_NF.efficiency_pump.fix(0.80) m.fs.pump_NF.control_volume.properties_out[0].pressure.fix(4e5) # inlet/outlet ports for pretreatment pretrt_port["out"] = m.fs.NF.permeate pretrt_port["waste"] = m.fs.NF.retentate return pretrt_port
def build(): # flowsheet set up m = ConcreteModel() m.fs = FlowsheetBlock(default={'dynamic': False}) m.fs.properties = props.NaClParameterBlock() m.fs.costing = WaterTAPCosting() # unit models m.fs.feed = Feed(default={'property_package': m.fs.properties}) m.fs.S1 = Separator(default={ "property_package": m.fs.properties, "outlet_list": ['P1', 'PXR'] }) m.fs.P1 = Pump(default={'property_package': m.fs.properties}) m.fs.PXR = PressureExchanger(default={'property_package': m.fs.properties}) m.fs.P2 = Pump(default={'property_package': m.fs.properties}) m.fs.M1 = Mixer( default={ "property_package": m.fs.properties, "momentum_mixing_type": MomentumMixingType.equality, # booster pump will match pressure "inlet_list": ['P1', 'P2'] }) m.fs.RO = ReverseOsmosis0D( default={ "property_package": m.fs.properties, "has_pressure_change": True, "pressure_change_type": PressureChangeType.calculated, "mass_transfer_coefficient": MassTransferCoefficient.calculated, "concentration_polarization_type": ConcentrationPolarizationType.calculated, }) m.fs.product = Product(default={'property_package': m.fs.properties}) m.fs.disposal = Product(default={'property_package': m.fs.properties}) # costing m.fs.costing.cost_flow( pyunits.convert(m.fs.P1.work_mechanical[0], to_units=pyunits.kW), "electricity") m.fs.costing.cost_flow( pyunits.convert(m.fs.P2.work_mechanical[0], to_units=pyunits.kW), "electricity") m.fs.P1.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.costing}) m.fs.P2.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.costing}) m.fs.RO.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.costing}) m.fs.PXR.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.costing}) m.fs.costing.cost_process() m.fs.costing.add_LCOW(m.fs.product.properties[0].flow_vol) m.fs.costing.add_specific_energy_consumption( m.fs.product.properties[0].flow_vol) # connections m.fs.s01 = Arc(source=m.fs.feed.outlet, destination=m.fs.S1.inlet) m.fs.s02 = Arc(source=m.fs.S1.P1, destination=m.fs.P1.inlet) m.fs.s03 = Arc(source=m.fs.P1.outlet, destination=m.fs.M1.P1) m.fs.s04 = Arc(source=m.fs.M1.outlet, destination=m.fs.RO.inlet) m.fs.s05 = Arc(source=m.fs.RO.permeate, destination=m.fs.product.inlet) m.fs.s06 = Arc(source=m.fs.RO.retentate, destination=m.fs.PXR.high_pressure_inlet) m.fs.s07 = Arc(source=m.fs.PXR.high_pressure_outlet, destination=m.fs.disposal.inlet) m.fs.s08 = Arc(source=m.fs.S1.PXR, destination=m.fs.PXR.low_pressure_inlet) m.fs.s09 = Arc(source=m.fs.PXR.low_pressure_outlet, destination=m.fs.P2.inlet) m.fs.s10 = Arc(source=m.fs.P2.outlet, destination=m.fs.M1.P2) TransformationFactory("network.expand_arcs").apply_to(m) # scaling # set default property values m.fs.properties.set_default_scaling('flow_mass_phase_comp', 1, index=('Liq', 'H2O')) m.fs.properties.set_default_scaling('flow_mass_phase_comp', 1e2, index=('Liq', 'NaCl')) # set unit model values iscale.set_scaling_factor(m.fs.P1.control_volume.work, 1e-3) iscale.set_scaling_factor(m.fs.P2.control_volume.work, 1e-3) iscale.set_scaling_factor(m.fs.PXR.low_pressure_side.work, 1e-3) iscale.set_scaling_factor(m.fs.PXR.high_pressure_side.work, 1e-3) # touch properties used in specifying and initializing the model m.fs.feed.properties[0].flow_vol_phase['Liq'] m.fs.feed.properties[0].mass_frac_phase_comp['Liq', 'NaCl'] m.fs.S1.mixed_state[0].mass_frac_phase_comp m.fs.S1.PXR_state[0].flow_vol_phase['Liq'] # unused scaling factors needed by IDAES base costing module # calculate and propagate scaling factors iscale.calculate_scaling_factors(m) 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, "material_balance_type": config.material_balance_type, "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)
class TurbineMultistageData(UnitModelBlockData): CONFIG = ConfigBlock() _define_turbine_multistage_config(CONFIG) 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, "material_balance_type": config.material_balance_type, "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 _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 s_cfg = copy.copy(unit_cfg) # splitter config based on unit_cfg s_cfg.update( split_basis=SplittingType.totalFlow, ideal_separation=False, num_outlets=no, energy_split_basis=EnergySplittingType.equal_molar_enthalpy, ) return s_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 """ m_cfg = copy.copy(unit_cfg) # splitter config based on unit_cfg m_cfg.update( num_inlets=ni, momentum_mixing_type=MomentumMixingType.minimize_and_equality ) return m_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 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="ipopt", optarg={"tol": 1e-6, "max_iter": 35}, copy_disconneted_flow=True, ): """ Initialize """ # sp is what to save to make sure state after init is same as the start # saves value, fixed, and active state, doesn't load originally free # values, this makes sure original problem spec is same but # initializes the values of free vars sp = StoreSpec.value_isfixed_isactive(only_fixed=True) istate = to_json(self, return_dict=True, wts=sp) ni = self.config.num_parallel_inlet_stages def init_section(stages, splits, disconnects, prev_port): if 0 in splits: _set_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: _set_port(stages[i].inlet, prev_port) else: if copy_disconneted_flow: for t in stages[i].stages[i].inlet.flow_mol: stages[i].inlet.flow_mol[t] = \ prev_port.flow_mol[t] stages[i].initialize( outlvl=outlvl, solver=solver, optarg=optarg) prev_port = stages[i].outlet if i in splits: _set_port(splits[i].inlet, prev_port) splits[i].initialize( outlvl=outlvl, solver=solver, optarg=optarg) prev_port = splits[i].outlet_1 return prev_port for k in [1, 2]: # Initialize Splitter # Fix n - 1 split fractions self.inlet_split.split_fraction[0, "outlet_1"].value = 1.0 / ni for i in self.inlet_stage_idx: if i == 1: # fix rest of splits at leaving first one free continue self.inlet_split.split_fraction[ 0, "outlet_{}".format(i)].fix(1.0 / ni) # fix inlet and free outlet self.inlet_split.inlet.fix() for i in self.inlet_stage_idx: ol = getattr(self.inlet_split, "outlet_{}".format(i)) ol.unfix() self.inlet_split.initialize( outlvl=outlvl, solver=solver, optarg=optarg) # free split fractions for i in self.inlet_stage_idx: self.inlet_split.split_fraction[ 0, "outlet_{}".format(i)].unfix() # Initialize valves for i in self.inlet_stage_idx: _set_port( self.throttle_valve[i].inlet, getattr(self.inlet_split, "outlet_{}".format(i)), ) self.throttle_valve[i].initialize( outlvl=outlvl, solver=solver, optarg=optarg ) # Initialize turbine for i in self.inlet_stage_idx: _set_port( self.inlet_stage[i].inlet, self.throttle_valve[i].outlet) self.inlet_stage[i].initialize( outlvl=outlvl, solver=solver, optarg=optarg ) # Initialize Mixer self.inlet_mix.use_minimum_inlet_pressure_constraint() for i in self.inlet_stage_idx: _set_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 = init_section( self.hp_stages, self.hp_split, self.config.hp_disconnect, prev_port ) if len(self.hp_stages) in self.config.hp_disconnect: prev_port = self.ip_stages[1].inlet prev_port = init_section( self.ip_stages, self.ip_split, self.config.ip_disconnect, prev_port ) if len(self.ip_stages) in self.config.ip_disconnect: prev_port = self.lp_stages[1].inlet prev_port = init_section( self.lp_stages, self.lp_split, self.config.lp_disconnect, prev_port ) _set_port(self.outlet_stage.inlet, prev_port) self.outlet_stage.initialize( outlvl=outlvl, solver=solver, optarg=optarg) for t in self.flowsheet().time: self.inlet_split.inlet.flow_mol[ t ].value = self.outlet_stage.inlet.flow_mol[t].value from_json(self, sd=istate, wts=sp)
def build(erd_type=None): # flowsheet set up m = ConcreteModel() m.db = Database() m.erd_type = erd_type m.fs = FlowsheetBlock(default={"dynamic": False}) m.fs.prop_prtrt = prop_ZO.WaterParameterBlock( default={"solute_list": ["tds", "tss"]} ) density = 1023.5 * pyunits.kg / pyunits.m**3 m.fs.prop_prtrt.dens_mass_default = density m.fs.prop_psttrt = prop_ZO.WaterParameterBlock(default={"solute_list": ["tds"]}) m.fs.prop_desal = prop_SW.SeawaterParameterBlock() # block structure prtrt = m.fs.pretreatment = Block() desal = m.fs.desalination = Block() psttrt = m.fs.posttreatment = Block() # unit models m.fs.feed = FeedZO(default={"property_package": m.fs.prop_prtrt}) # pretreatment prtrt.intake = SWOnshoreIntakeZO(default={"property_package": m.fs.prop_prtrt}) prtrt.ferric_chloride_addition = ChemicalAdditionZO( default={ "property_package": m.fs.prop_prtrt, "database": m.db, "process_subtype": "ferric_chloride", } ) prtrt.chlorination = ChlorinationZO( default={"property_package": m.fs.prop_prtrt, "database": m.db} ) prtrt.static_mixer = StaticMixerZO( default={"property_package": m.fs.prop_prtrt, "database": m.db} ) prtrt.storage_tank_1 = StorageTankZO( default={"property_package": m.fs.prop_prtrt, "database": m.db} ) prtrt.media_filtration = MediaFiltrationZO( default={"property_package": m.fs.prop_prtrt, "database": m.db} ) prtrt.backwash_handling = BackwashSolidsHandlingZO( default={"property_package": m.fs.prop_prtrt, "database": m.db} ) prtrt.anti_scalant_addition = ChemicalAdditionZO( default={ "property_package": m.fs.prop_prtrt, "database": m.db, "process_subtype": "anti-scalant", } ) prtrt.cartridge_filtration = CartridgeFiltrationZO( default={"property_package": m.fs.prop_prtrt, "database": m.db} ) # desalination desal.P1 = Pump(default={"property_package": m.fs.prop_desal}) desal.RO = ReverseOsmosis0D( default={ "property_package": m.fs.prop_desal, "has_pressure_change": True, "pressure_change_type": PressureChangeType.calculated, "mass_transfer_coefficient": MassTransferCoefficient.calculated, "concentration_polarization_type": ConcentrationPolarizationType.calculated, } ) desal.RO.width.setub(5000) desal.RO.area.setub(20000) if erd_type == "pressure_exchanger": desal.S1 = Separator( default={"property_package": m.fs.prop_desal, "outlet_list": ["P1", "PXR"]} ) desal.M1 = Mixer( default={ "property_package": m.fs.prop_desal, "momentum_mixing_type": MomentumMixingType.equality, # booster pump will match pressure "inlet_list": ["P1", "P2"], } ) desal.PXR = PressureExchanger(default={"property_package": m.fs.prop_desal}) desal.P2 = Pump(default={"property_package": m.fs.prop_desal}) elif erd_type == "pump_as_turbine": desal.ERD = EnergyRecoveryDevice(default={"property_package": m.fs.prop_desal}) else: raise ConfigurationError( "erd_type was {}, but can only " "be pressure_exchanger or pump_as_turbine" "".format(erd_type) ) # posttreatment psttrt.storage_tank_2 = StorageTankZO( default={"property_package": m.fs.prop_psttrt, "database": m.db} ) psttrt.uv_aop = UVAOPZO( default={ "property_package": m.fs.prop_psttrt, "database": m.db, "process_subtype": "hydrogen_peroxide", } ) psttrt.co2_addition = CO2AdditionZO( default={"property_package": m.fs.prop_psttrt, "database": m.db} ) psttrt.lime_addition = ChemicalAdditionZO( default={ "property_package": m.fs.prop_psttrt, "database": m.db, "process_subtype": "lime", } ) psttrt.storage_tank_3 = StorageTankZO( default={"property_package": m.fs.prop_psttrt, "database": m.db} ) # product and disposal m.fs.municipal = MunicipalDrinkingZO( default={"property_package": m.fs.prop_psttrt, "database": m.db} ) m.fs.landfill = LandfillZO( default={"property_package": m.fs.prop_prtrt, "database": m.db} ) m.fs.disposal = Product(default={"property_package": m.fs.prop_desal}) # translator blocks m.fs.tb_prtrt_desal = Translator( default={ "inlet_property_package": m.fs.prop_prtrt, "outlet_property_package": m.fs.prop_desal, } ) @m.fs.tb_prtrt_desal.Constraint(["H2O", "tds"]) def eq_flow_mass_comp(blk, j): if j == "tds": jj = "TDS" else: jj = j return ( blk.properties_in[0].flow_mass_comp[j] == blk.properties_out[0].flow_mass_phase_comp["Liq", jj] ) m.fs.tb_desal_psttrt = Translator( default={ "inlet_property_package": m.fs.prop_desal, "outlet_property_package": m.fs.prop_psttrt, } ) @m.fs.tb_desal_psttrt.Constraint(["H2O", "TDS"]) def eq_flow_mass_comp(blk, j): if j == "TDS": jj = "tds" else: jj = j return ( blk.properties_in[0].flow_mass_phase_comp["Liq", j] == blk.properties_out[0].flow_mass_comp[jj] ) # connections m.fs.s_feed = Arc(source=m.fs.feed.outlet, destination=prtrt.intake.inlet) prtrt.s01 = Arc( source=prtrt.intake.outlet, destination=prtrt.ferric_chloride_addition.inlet ) prtrt.s02 = Arc( source=prtrt.ferric_chloride_addition.outlet, destination=prtrt.chlorination.inlet, ) prtrt.s03 = Arc( source=prtrt.chlorination.treated, destination=prtrt.static_mixer.inlet ) prtrt.s04 = Arc( source=prtrt.static_mixer.outlet, destination=prtrt.storage_tank_1.inlet ) prtrt.s05 = Arc( source=prtrt.storage_tank_1.outlet, destination=prtrt.media_filtration.inlet ) prtrt.s06 = Arc( source=prtrt.media_filtration.byproduct, destination=prtrt.backwash_handling.inlet, ) prtrt.s07 = Arc( source=prtrt.media_filtration.treated, destination=prtrt.anti_scalant_addition.inlet, ) prtrt.s08 = Arc( source=prtrt.anti_scalant_addition.outlet, destination=prtrt.cartridge_filtration.inlet, ) m.fs.s_prtrt_tb = Arc( source=prtrt.cartridge_filtration.treated, destination=m.fs.tb_prtrt_desal.inlet ) m.fs.s_landfill = Arc( source=prtrt.backwash_handling.byproduct, destination=m.fs.landfill.inlet ) if erd_type == "pressure_exchanger": m.fs.s_tb_desal = Arc( source=m.fs.tb_prtrt_desal.outlet, destination=desal.S1.inlet ) desal.s01 = Arc(source=desal.S1.P1, destination=desal.P1.inlet) desal.s02 = Arc(source=desal.P1.outlet, destination=desal.M1.P1) desal.s03 = Arc(source=desal.M1.outlet, destination=desal.RO.inlet) desal.s04 = Arc( source=desal.RO.retentate, destination=desal.PXR.high_pressure_inlet ) desal.s05 = Arc(source=desal.S1.PXR, destination=desal.PXR.low_pressure_inlet) desal.s06 = Arc( source=desal.PXR.low_pressure_outlet, destination=desal.P2.inlet ) desal.s07 = Arc(source=desal.P2.outlet, destination=desal.M1.P2) m.fs.s_disposal = Arc( source=desal.PXR.high_pressure_outlet, destination=m.fs.disposal.inlet ) elif erd_type == "pump_as_turbine": m.fs.s_tb_desal = Arc( source=m.fs.tb_prtrt_desal.outlet, destination=desal.P1.inlet ) desal.s01 = Arc(source=desal.P1.outlet, destination=desal.RO.inlet) desal.s02 = Arc(source=desal.RO.retentate, destination=desal.ERD.inlet) m.fs.s_disposal = Arc(source=desal.ERD.outlet, destination=m.fs.disposal.inlet) m.fs.s_desal_tb = Arc( source=desal.RO.permeate, destination=m.fs.tb_desal_psttrt.inlet ) m.fs.s_tb_psttrt = Arc( source=m.fs.tb_desal_psttrt.outlet, destination=psttrt.storage_tank_2.inlet ) psttrt.s01 = Arc( source=psttrt.storage_tank_2.outlet, destination=psttrt.uv_aop.inlet ) psttrt.s02 = Arc( source=psttrt.uv_aop.treated, destination=psttrt.co2_addition.inlet ) psttrt.s03 = Arc( source=psttrt.co2_addition.outlet, destination=psttrt.lime_addition.inlet ) psttrt.s04 = Arc( source=psttrt.lime_addition.outlet, destination=psttrt.storage_tank_3.inlet ) m.fs.s_municipal = Arc( source=psttrt.storage_tank_3.outlet, destination=m.fs.municipal.inlet ) TransformationFactory("network.expand_arcs").apply_to(m) # scaling # set default property values m.fs.prop_desal.set_default_scaling( "flow_mass_phase_comp", 1e-3, index=("Liq", "H2O") ) m.fs.prop_desal.set_default_scaling( "flow_mass_phase_comp", 1e-1, index=("Liq", "TDS") ) # set unit model values iscale.set_scaling_factor(desal.P1.control_volume.work, 1e-5) iscale.set_scaling_factor(desal.RO.area, 1e-4) if erd_type == "pressure_exchanger": iscale.set_scaling_factor(desal.P2.control_volume.work, 1e-5) iscale.set_scaling_factor(desal.PXR.low_pressure_side.work, 1e-5) iscale.set_scaling_factor(desal.PXR.high_pressure_side.work, 1e-5) elif erd_type == "pump_as_turbine": iscale.set_scaling_factor(desal.ERD.control_volume.work, 1e-5) # calculate and propagate scaling factors iscale.calculate_scaling_factors(m) return m