def test_initialize(self, Crystallizer_frame): # Add costing function, then initialize m = Crystallizer_frame m.fs.costing = WaterTAPCosting() m.fs.unit.costing = UnitModelCostingBlock(default={ "flowsheet_costing_block": m.fs.costing, "costing_method_arguments": { "cost_type": CrystallizerCostType.mass_basis }, }, ) m.fs.costing.cost_process() initialization_tester(Crystallizer_frame)
def build(number_of_stages=2): # ---building model--- m = ConcreteModel() m.fs = FlowsheetBlock(default={"dynamic": False}) m.fs.properties = props.NaClParameterBlock() m.fs.costing = WaterTAPCosting() m.fs.NumberOfStages = Param(initialize=number_of_stages) m.fs.StageSet = RangeSet(m.fs.NumberOfStages) m.fs.LSRRO_StageSet = RangeSet(2, m.fs.NumberOfStages) m.fs.NonFinal_StageSet = RangeSet(m.fs.NumberOfStages-1) m.fs.feed = Feed(default={'property_package': m.fs.properties}) m.fs.product = Product(default={'property_package': m.fs.properties}) m.fs.disposal = Product(default={'property_package': m.fs.properties}) # Add the mixers m.fs.Mixers = Mixer(m.fs.NonFinal_StageSet, default={ "property_package": m.fs.properties, "momentum_mixing_type": MomentumMixingType.equality, # booster pump will match pressure "inlet_list": ['upstream', 'downstream']}) total_pump_work = 0 # Add the pumps m.fs.PrimaryPumps = Pump(m.fs.StageSet, default={"property_package": m.fs.properties}) for pump in m.fs.PrimaryPumps.values(): pump.costing = UnitModelCostingBlock(default={ "flowsheet_costing_block":m.fs.costing}) m.fs.costing.cost_flow(pyunits.convert(pump.work_mechanical[0], to_units=pyunits.kW), "electricity") # Add the equalizer pumps m.fs.BoosterPumps = Pump(m.fs.LSRRO_StageSet, default={"property_package": m.fs.properties}) for pump in m.fs.BoosterPumps.values(): pump.costing = UnitModelCostingBlock(default={ "flowsheet_costing_block":m.fs.costing}) m.fs.costing.cost_flow(pyunits.convert(pump.work_mechanical[0], to_units=pyunits.kW), "electricity") # Add the stages ROs m.fs.ROUnits = ReverseOsmosis0D(m.fs.StageSet, 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}) for ro_unit in m.fs.ROUnits.values(): ro_unit.costing = UnitModelCostingBlock(default={ "flowsheet_costing_block":m.fs.costing}) # Add EnergyRecoveryDevice m.fs.EnergyRecoveryDevice = Pump(default={"property_package": m.fs.properties}) m.fs.EnergyRecoveryDevice.costing = UnitModelCostingBlock(default={ "flowsheet_costing_block":m.fs.costing, "costing_method_arguments":{"pump_type":PumpType.energy_recovery_device}}) m.fs.costing.cost_flow(pyunits.convert(m.fs.EnergyRecoveryDevice.work_mechanical[0], to_units=pyunits.kW), "electricity") # additional variables or expressions # system water recovery m.fs.water_recovery = Var( initialize=0.5, bounds=(0, 1), domain=NonNegativeReals, units=pyunits.dimensionless, doc='System Water Recovery') m.fs.eq_water_recovery = Constraint(expr=\ sum(m.fs.feed.flow_mass_phase_comp[0,'Liq',:]) * m.fs.water_recovery == \ sum(m.fs.product.flow_mass_phase_comp[0,'Liq',:]) ) # costing m.fs.costing.cost_process() product_flow_vol_total = m.fs.product.properties[0].flow_vol m.fs.costing.add_LCOW(product_flow_vol_total) m.fs.costing.add_specific_energy_consumption(product_flow_vol_total) # objective m.fs.objective = Objective(expr=m.fs.costing.LCOW) # connections # Connect the feed to the first pump m.fs.feed_to_pump = Arc(source=m.fs.feed.outlet, destination=m.fs.PrimaryPumps[1].inlet) # Connect the primary RO permeate to the product m.fs.primary_RO_to_product = Arc(source=m.fs.ROUnits[1].permeate, destination=m.fs.product.inlet) # Connect the Pump n to the Mixer n m.fs.pump_to_mixer = Arc(m.fs.NonFinal_StageSet, rule=lambda fs,n : {'source':fs.PrimaryPumps[n].outlet, 'destination':fs.Mixers[n].upstream}) # Connect the Mixer n to the Stage n m.fs.mixer_to_stage = Arc(m.fs.NonFinal_StageSet, rule=lambda fs,n : {'source':fs.Mixers[n].outlet, 'destination':fs.ROUnits[n].inlet}) # Connect the Stage n to the Pump n+1 m.fs.stage_to_pump = Arc(m.fs.NonFinal_StageSet, rule=lambda fs,n : {'source':fs.ROUnits[n].retentate, 'destination':fs.PrimaryPumps[n+1].inlet}) # Connect the Stage n to the Eq Pump n m.fs.stage_to_eq_pump = Arc(m.fs.LSRRO_StageSet, rule=lambda fs,n : {'source':fs.ROUnits[n].permeate, 'destination':fs.BoosterPumps[n].inlet}) # Connect the Eq Pump n to the Mixer n-1 m.fs.eq_pump_to_mixer = Arc(m.fs.LSRRO_StageSet, rule=lambda fs,n : {'source':fs.BoosterPumps[n].outlet, 'destination':fs.Mixers[n-1].downstream}) # Connect the Pump N to the Stage N last_stage = m.fs.StageSet.last() m.fs.pump_to_stage = Arc(source=m.fs.PrimaryPumps[last_stage].outlet, destination=m.fs.ROUnits[last_stage].inlet) # Connect Final Stage to EnergyRecoveryDevice Pump m.fs.stage_to_erd = Arc(source=m.fs.ROUnits[last_stage].retentate, destination=m.fs.EnergyRecoveryDevice.inlet) # Connect the EnergyRecoveryDevice to the disposal m.fs.erd_to_disposal = Arc(source=m.fs.EnergyRecoveryDevice.outlet, destination=m.fs.disposal.inlet) # additional bounding for b in m.component_data_objects(Block, descend_into=True): # NaCl solubility limit if hasattr(b, 'mass_frac_phase_comp'): b.mass_frac_phase_comp['Liq', 'NaCl'].setub(0.26) 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() 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
log_close_to_bounds, log_infeasible_bounds, log_infeasible_constraints, ) from pyomo.common.log import LoggingIntercept import logging if __name__ == "__main__": # create model, flowsheet m = ConcreteModel() m.fs = FlowsheetBlock(default={"dynamic": False}) # attach property package m.fs.properties = props.NaClParameterBlock() m.fs.costing = WaterTAPCosting() # build the unit model m.fs.crystallizer = Crystallization(default={"property_package": m.fs.properties}) # now specify the model print("DOF before specifying:", degrees_of_freedom(m.fs)) # Specify the Feed m.fs.crystallizer.inlet.flow_mass_phase_comp[0, "Liq", "NaCl"].fix(10.5119) m.fs.crystallizer.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(38.9326) # m.fs.crystallizer.properties_in[0].flow_vol_phase['Liq'].fix(150/3600) # m.fs.crystallizer.properties_in[0].mass_frac_phase_comp['Liq', 'NaCl'].fix(0.2126) m.fs.crystallizer.inlet.flow_mass_phase_comp[0, "Sol", "NaCl"].fix(1e-6) m.fs.crystallizer.inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(1e-6) m.fs.crystallizer.inlet.pressure[0].fix(101325) m.fs.crystallizer.inlet.temperature[0].fix(273.15 + 20)
def add_costing(m): prtrt = m.fs.pretreatment desal = m.fs.desalination psttrt = m.fs.posttreatment # Add costing package for zero-order units m.fs.zo_costing = ZeroOrderCosting() m.fs.ro_costing = WaterTAPCosting() # Add costing to zero order units # Pre-treatment units # This really looks like it should be a feed block in its own right # prtrt.intake.costing = UnitModelCostingBlock(default={ # "flowsheet_costing_block": m.fs.zo_costing}) prtrt.ferric_chloride_addition.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) prtrt.chlorination.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) prtrt.static_mixer.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) prtrt.storage_tank_1.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) prtrt.media_filtration.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) prtrt.backwash_handling.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) prtrt.anti_scalant_addition.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) prtrt.cartridge_filtration.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) # RO Train # RO equipment is costed using more detailed costing package desal.P1.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.ro_costing} ) desal.RO.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.ro_costing} ) if m.erd_type == "pressure_exchanger": # desal.S1.costing = UnitModelCostingBlock(default={ # "flowsheet_costing_block": m.fs.ro_costing}) desal.M1.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.ro_costing} ) desal.PXR.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.ro_costing} ) desal.P2.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.ro_costing} ) elif m.erd_type == "pump_as_turbine": pass # desal.ERD.costing = UnitModelCostingBlock(default={ # "flowsheet_costing_block": m.fs.ro_costing}) else: raise ConfigurationError( f"erd_type was {m.erd_type}, costing only implemented " "for pressure_exchanger or pump_as_turbine" ) # For non-zero order unit operations, we need to register costed flows # separately. # However, to keep costs consistent, we will register these with the ZO # Costing package m.fs.zo_costing.cost_flow(desal.P1.work_mechanical[0], "electricity") if m.erd_type == "pressure_exchanger": m.fs.zo_costing.cost_flow(desal.P2.work_mechanical[0], "electricity") elif m.erd_type == "pump_as_turbine": pass # m.fs.zo_costing.cost_flow( # desal.ERD.work_mechanical[0], "electricity") else: raise ConfigurationError( f"erd_type was {m.erd_type}, costing only implemented " "for pressure_exchanger or pump_as_turbine" ) # Post-treatment units psttrt.storage_tank_2.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) psttrt.uv_aop.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) psttrt.co2_addition.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) psttrt.lime_addition.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) psttrt.storage_tank_3.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) # Product and disposal m.fs.municipal.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) m.fs.landfill.costing = UnitModelCostingBlock( default={"flowsheet_costing_block": m.fs.zo_costing} ) # Aggregate unit level costs and calculate overall process costs m.fs.zo_costing.cost_process() m.fs.ro_costing.cost_process() # Combine results from costing packages and calculate overall metrics @m.Expression() def total_capital_cost(b): return ( pyunits.convert( m.fs.zo_costing.total_capital_cost, to_units=pyunits.USD_2018 ) + m.fs.ro_costing.total_investment_cost ) @m.Expression() def total_operating_cost(b): return ( pyunits.convert( m.fs.zo_costing.total_fixed_operating_cost, to_units=pyunits.USD_2018 / pyunits.year, ) + pyunits.convert( m.fs.zo_costing.total_variable_operating_cost, to_units=pyunits.USD_2018 / pyunits.year, ) + m.fs.ro_costing.total_operating_cost ) @m.Expression() def LCOW(b): return ( b.total_capital_cost * b.fs.zo_costing.capital_recovery_factor + b.total_operating_cost ) / ( pyunits.convert( b.fs.municipal.properties[0].flow_vol, to_units=pyunits.m**3 / pyunits.year, ) * b.fs.zo_costing.utilization_factor ) assert_units_consistent(m)