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_ZONF(m, base='ion'): """ Builds a ZONF model based on specified rejection and solvent flux. Requires prop_ion property package. """ if base not in ['ion']: raise ValueError('Unexpected property base {base} for build_ZONF' ''.format(base=base)) prop = property_models.get_prop(m, base=base) m.fs.NF = NanofiltrationZO(default={ "property_package": prop, "has_pressure_change": False }) # specify m.fs.NF.flux_vol_solvent.fix(1.67e-6) m.fs.NF.area.fix(500) m.fs.NF.properties_permeate[0].pressure.fix(101325) m.fs.NF.rejection_phase_comp[0, 'Liq', 'Na'].fix(0.01) m.fs.NF.rejection_phase_comp[0, 'Liq', 'Ca'].fix(0.79) m.fs.NF.rejection_phase_comp[0, 'Liq', 'Mg'].fix(0.94) m.fs.NF.rejection_phase_comp[0, 'Liq', 'SO4'].fix(0.87) m.fs.NF.rejection_phase_comp[ 0, 'Liq', 'Cl'] = 0.15 # guess, but electroneutrality enforced below charge_comp = {'Na': 1, 'Ca': 2, 'Mg': 2, 'SO4': -2, 'Cl': -1} m.fs.NF.eq_electroneutrality = Constraint(expr=0 == sum( charge_comp[j] * m.fs.NF.feed_side.properties_out[0].conc_mol_phase_comp['Liq', j] for j in charge_comp)) constraint_scaling_transform(m.fs.NF.eq_electroneutrality, 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 build_RO(m, base="TDS", level="simple", name_str="RO"): """ Builds a 1DRO model at a specified level (simple or detailed). Requires prop_TDS property package. """ if base not in ["TDS"]: raise ValueError("Unexpected property base {base} for build_RO" "".format(base=base)) prop = property_models.get_prop(m, base=base) if level == "simple": raise ValueError("Unexpected RO level {level} for build_RO" "".format(level=level)) elif level == "detailed": # build unit setattr( m.fs, name_str, ReverseOsmosis1D( default={ "property_package": prop, "has_pressure_change": True, "pressure_change_type": PressureChangeType.calculated, "mass_transfer_coefficient": MassTransferCoefficient.calculated, "concentration_polarization_type": ConcentrationPolarizationType.calculated, "transformation_scheme": "BACKWARD", "transformation_method": "dae.finite_difference", "finite_elements": 10, }), ) blk = getattr(m.fs, name_str) # specify unit blk.area.fix(50) blk.A_comp.fix(4.2e-12) blk.B_comp.fix(3.5e-8) blk.mixed_permeate[0].pressure.fix(101325) blk.channel_height.fix(1e-3) blk.spacer_porosity.fix(0.97) blk.N_Re[0, 0].fix(500) else: raise ValueError("Unexpected argument {level} for level in build_RO" "".format(level=level))
def build_feed(m, base='TDS'): """ Build a feed block for a specified property base. The state vars are fixed to the standard condition. Property bases include: 'TDS', 'ion', 'salt' """ # feed block prop = property_models.get_prop(m, base=base) # build m.fs.feed = Feed(default={'property_package': prop}) # specify property_models.specify_feed(m.fs.feed.properties[0], base=base) m.fs.feed.properties[0].mass_frac_phase_comp # touch so the block can be initialized
def build_desalination( m, has_desal_feed=False, is_twostage=False, has_ERD=False, RO_type="0D", RO_base="TDS", RO_level="simple", ): """ Builds RO desalination including specified feed and auxiliary equipment. Args: has_desal_feed: True or False, default = False, if True a feed block is created and specified to the standard feed RO_type: 'Sep', '0D', or 1D, default = '0D' RO_level: 'simple' or 'detailed', default = 'simple' RO_base: 'TDS' only, default = 'ion' """ desal_port = {} prop = property_models.get_prop(m, base=RO_base) if has_desal_feed: # build feed feed_block.build_feed(m, base=RO_base) # build RO if RO_type == "Sep": unit_separator.build_SepRO(m, base=RO_base) elif RO_type == "0D": unit_0DRO.build_RO(m, base="TDS", level=RO_level) elif RO_type == "1D": unit_1DRO.build_RO(m, base="TDS", level=RO_level) else: raise ValueError( "Unexpected model type {RO_type} provided to build_desalination" "".format(RO_type=RO_type)) if is_twostage: if RO_type == "0D": unit_0DRO.build_RO(m, base="TDS", level=RO_level, name_str="RO2") elif RO_type == "1D": unit_1DRO.build_RO(m, base="TDS", level=RO_level, name_str="RO2") else: raise ValueError( "Unexpected model type {RO_type} provided to build_desalination when is_twostage is True" "".format(RO_type=RO_type)) if has_ERD: if RO_type == "Sep": raise ValueError( "Unexpected model type {RO_type} provided to build_desalination when has_ERD is True" "".format(RO_type=RO_type)) m.fs.ERD = Pump(default={"property_package": prop}) m.fs.ERD.actual_work.deactivate( ) # this constraint is for pumps, not turbines m.fs.ERD.turbine_work = Constraint( expr=m.fs.ERD.work_fluid[0] * m.fs.ERD.efficiency_pump[0] == m.fs.ERD.control_volume.work[0]) # auxiliary units if RO_type == "Sep": # build auxiliary units (none) # connect models if has_desal_feed: m.fs.s_desal_feed_RO = Arc(source=m.fs.feed.outlet, destination=m.fs.RO.inlet) # specify (RO already specified) # inlet/outlet ports for pretreatment if not has_desal_feed: desal_port["in"] = m.fs.RO.inlet elif RO_type == "0D" or RO_type == "1D": # build auxiliary units m.fs.pump_RO = Pump(default={"property_package": prop}) if is_twostage: m.fs.pump_RO2 = Pump(default={"property_package": prop}) m.fs.mixer_permeate = Mixer(default={ "property_package": prop, "inlet_list": ["RO", "RO2"] }) # connect models if has_desal_feed: m.fs.s_desal_feed_pumpRO = Arc(source=m.fs.feed.outlet, destination=m.fs.pump_RO.inlet) m.fs.s_desal_pumpRO_RO = Arc(source=m.fs.pump_RO.outlet, destination=m.fs.RO.inlet) if is_twostage: m.fs.s_desal_RO_pumpRO2 = Arc(source=m.fs.RO.retentate, destination=m.fs.pump_RO2.inlet) m.fs.s_desal_pumpRO2_RO2 = Arc(source=m.fs.pump_RO2.outlet, destination=m.fs.RO2.inlet) m.fs.s_desal_permeateRO_mixer = Arc( source=m.fs.RO.permeate, destination=m.fs.mixer_permeate.RO) m.fs.s_desal_permeateRO2_mixer = Arc( source=m.fs.RO2.permeate, destination=m.fs.mixer_permeate.RO2) if has_ERD: if is_twostage: m.fs.s_desal_RO2_ERD = Arc(source=m.fs.RO2.retentate, destination=m.fs.ERD.inlet) else: m.fs.s_desal_RO_ERD = Arc(source=m.fs.RO.retentate, destination=m.fs.ERD.inlet) # specify (RO already specified, Pump 2 DOF, ERD 2 DOF) m.fs.pump_RO.efficiency_pump.fix(0.80) m.fs.pump_RO.control_volume.properties_out[0].pressure.fix(50e5) if is_twostage: m.fs.pump_RO2.efficiency_pump.fix(0.80) m.fs.pump_RO2.control_volume.properties_out[0].pressure.fix(55e5) if has_ERD: m.fs.ERD.efficiency_pump.fix(0.95) m.fs.ERD.outlet.pressure[0].fix(101325) # inlet/outlet ports for pretreatment if not has_desal_feed: desal_port["in"] = m.fs.pump_RO.inlet if is_twostage: desal_port["out"] = m.fs.mixer_permeate.outlet if has_ERD: desal_port["waste"] = m.fs.ERD.outlet else: desal_port["waste"] = m.fs.RO2.retentate else: desal_port["out"] = m.fs.RO.permeate if has_ERD: desal_port["waste"] = m.fs.ERD.outlet else: desal_port["waste"] = m.fs.RO.retentate return desal_port
def build_RO(m, base='TDS', level='simple', name_str='RO'): """ Builds a 0DRO model at a specified level (simple or detailed). Requires prop_TDS property package. """ if base not in ['TDS']: raise ValueError('Unexpected property base {base} for build_RO' ''.format(base=base)) prop = property_models.get_prop(m, base=base) if level == 'simple': # build unit setattr( m.fs, name_str, ReverseOsmosis0D( default={ "property_package": prop, "mass_transfer_coefficient": MassTransferCoefficient.none, "concentration_polarization_type": ConcentrationPolarizationType.none })) blk = getattr(m.fs, name_str) # specify unit blk.area.fix(50) blk.A_comp.fix(4.2e-12) blk.B_comp.fix(3.5e-8) blk.permeate.pressure[0].fix(101325) elif level == 'detailed': # build unit setattr( m.fs, name_str, ReverseOsmosis0D( default={ "property_package": prop, "has_pressure_change": True, "pressure_change_type": PressureChangeType.calculated, "mass_transfer_coefficient": MassTransferCoefficient.calculated, "concentration_polarization_type": ConcentrationPolarizationType.calculated })) blk = getattr(m.fs, name_str) # specify unit blk.area.fix(50) blk.A_comp.fix(4.2e-12) blk.B_comp.fix(3.5e-8) blk.permeate.pressure[0].fix(101325) blk.channel_height.fix(1e-3) blk.spacer_porosity.fix(0.97) blk.N_Re[0, 0].fix(500) else: raise ValueError('Unexpected argument {level} for level in build_RO' ''.format(level=level))
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_tb(m, base_inlet='ion', base_outlet='TDS', name_str=None): """ Build a translator block to convert for the specified base from inlet to outlet. """ if name_str is None: name_str = 'tb_' + base_inlet + '_to_' + base_outlet if base_inlet not in ['ion', 'salt']: raise ValueError( 'Unexpected property base inlet {base_inlet} for build_tb' ''.format(base_inlet=base_inlet)) prop_inlet = property_models.get_prop(m, base=base_inlet) if base_outlet not in ['TDS']: raise ValueError( 'Unexpected property base outlet {base_outlet} for build_tb' ''.format(base_outlet=base_outlet)) prop_outlet = property_models.get_prop(m, base=base_outlet) # build translator block setattr( m.fs, name_str, Translator( default={ "inlet_property_package": prop_inlet, "outlet_property_package": prop_outlet })) blk = getattr(m.fs, name_str) # scale translator block to get scaling factors calculate_scaling_factors(blk) # add translator block constraints blk.eq_equal_temperature = Constraint( expr=blk.inlet.temperature[0] == blk.outlet.temperature[0]) constraint_scaling_transform( blk.eq_equal_temperature, get_scaling_factor(blk.properties_in[0].temperature)) blk.eq_equal_pressure = Constraint( expr=blk.inlet.pressure[0] == blk.outlet.pressure[0]) constraint_scaling_transform( blk.eq_equal_pressure, get_scaling_factor(blk.properties_in[0].pressure)) if base_inlet == 'ion' and base_outlet == 'TDS': blk.eq_H2O_balance = Constraint(expr=blk.inlet.flow_mass_phase_comp[ 0, 'Liq', 'H2O'] == blk.outlet.flow_mass_phase_comp[0, 'Liq', 'H2O']) constraint_scaling_transform( blk.eq_H2O_balance, get_scaling_factor( blk.properties_out[0].flow_mass_phase_comp['Liq', 'H2O'])) blk.eq_TDS_balance = Constraint(expr=sum( blk.inlet.flow_mass_phase_comp[0, 'Liq', j] for j in ['Na', 'Ca', 'Mg', 'SO4', 'Cl' ]) == blk.outlet.flow_mass_phase_comp[0, 'Liq', 'TDS']) constraint_scaling_transform( blk.eq_TDS_balance, get_scaling_factor( blk.properties_out[0].flow_mass_phase_comp['Liq', 'TDS'])) elif base_inlet == 'salt' and base_outlet == 'TDS': blk.eq_H2O_balance = Constraint(expr=blk.inlet.flow_mass_phase_comp[ 0, 'Liq', 'H2O'] == blk.outlet.flow_mass_phase_comp[0, 'Liq', 'H2O']) constraint_scaling_transform( blk.eq_H2O_balance, get_scaling_factor( blk.properties_out[0].flow_mass_phase_comp['Liq', 'H2O'])) blk.eq_TDS_balance = Constraint(expr=sum( blk.inlet.flow_mass_phase_comp[0, 'Liq', j] for j in ['NaCl', 'CaSO4', 'MgSO4', 'MgCl2' ]) == blk.outlet.flow_mass_phase_comp[0, 'Liq', 'TDS']) constraint_scaling_transform( blk.eq_TDS_balance, get_scaling_factor( blk.properties_out[0].flow_mass_phase_comp['Liq', 'TDS'])) else: raise ValueError('Unexpected property base combination for build_tb') blk.properties_in[0].mass_frac_phase_comp # touch for initialization blk.properties_out[0].mass_frac_phase_comp
def build_tb(m, base_inlet="ion", base_outlet="TDS", name_str=None): """ Build a translator block to convert for the specified base from inlet to outlet. """ if name_str is None: name_str = "tb_" + base_inlet + "_to_" + base_outlet if base_inlet not in ["ion", "salt"]: raise ValueError( "Unexpected property base inlet {base_inlet} for build_tb" "".format(base_inlet=base_inlet)) prop_inlet = property_models.get_prop(m, base=base_inlet) if base_outlet not in ["TDS"]: raise ValueError( "Unexpected property base outlet {base_outlet} for build_tb" "".format(base_outlet=base_outlet)) prop_outlet = property_models.get_prop(m, base=base_outlet) # build translator block setattr( m.fs, name_str, Translator( default={ "inlet_property_package": prop_inlet, "outlet_property_package": prop_outlet, }), ) blk = getattr(m.fs, name_str) # scale translator block to get scaling factors calculate_scaling_factors(blk) # add translator block constraints blk.eq_equal_temperature = Constraint( expr=blk.inlet.temperature[0] == blk.outlet.temperature[0]) constraint_scaling_transform( blk.eq_equal_temperature, get_scaling_factor(blk.properties_in[0].temperature)) blk.eq_equal_pressure = Constraint( expr=blk.inlet.pressure[0] == blk.outlet.pressure[0]) constraint_scaling_transform( blk.eq_equal_pressure, get_scaling_factor(blk.properties_in[0].pressure)) if base_inlet == "ion" and base_outlet == "TDS": blk.eq_H2O_balance = Constraint(expr=blk.inlet.flow_mass_phase_comp[ 0, "Liq", "H2O"] == blk.outlet.flow_mass_phase_comp[0, "Liq", "H2O"]) constraint_scaling_transform( blk.eq_H2O_balance, get_scaling_factor( blk.properties_out[0].flow_mass_phase_comp["Liq", "H2O"]), ) blk.eq_TDS_balance = Constraint(expr=sum( blk.inlet.flow_mass_phase_comp[0, "Liq", j] for j in ["Na", "Ca", "Mg", "SO4", "Cl" ]) == blk.outlet.flow_mass_phase_comp[0, "Liq", "TDS"]) constraint_scaling_transform( blk.eq_TDS_balance, get_scaling_factor( blk.properties_out[0].flow_mass_phase_comp["Liq", "TDS"]), ) elif base_inlet == "salt" and base_outlet == "TDS": blk.eq_H2O_balance = Constraint(expr=blk.inlet.flow_mass_phase_comp[ 0, "Liq", "H2O"] == blk.outlet.flow_mass_phase_comp[0, "Liq", "H2O"]) constraint_scaling_transform( blk.eq_H2O_balance, get_scaling_factor( blk.properties_out[0].flow_mass_phase_comp["Liq", "H2O"]), ) blk.eq_TDS_balance = Constraint(expr=sum( blk.inlet.flow_mass_phase_comp[0, "Liq", j] for j in ["NaCl", "CaSO4", "MgSO4", "MgCl2" ]) == blk.outlet.flow_mass_phase_comp[0, "Liq", "TDS"]) constraint_scaling_transform( blk.eq_TDS_balance, get_scaling_factor( blk.properties_out[0].flow_mass_phase_comp["Liq", "TDS"]), ) else: raise ValueError("Unexpected property base combination for build_tb") blk.properties_in[0].mass_frac_phase_comp # touch for initialization blk.properties_out[0].mass_frac_phase_comp