###############################################################################
# WaterTAP Copyright (c) 2021, The Regents of the University of California,
# through Lawrence Berkeley National Laboratory, Oak Ridge National
# Laboratory, National Renewable Energy Laboratory, and National Energy
# Technology Laboratory (subject to receipt of any required approvals from
# the U.S. Dept. of Energy). All rights reserved.
#
# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license
# information, respectively. These files are also available online at the URL
# "https://github.com/watertap-org/watertap/"
#
###############################################################################
from pyomo.environ import (
Block,
ConcreteModel,
Constraint,
Expression,
Var,
Param,
value,
TransformationFactory,
units as pyunits,
)
from idaes.generic_models.costing import UnitModelCostingBlock
from watertap.costing import WaterTAPCosting, PumpType, MixerType, ROType
from watertap.examples.flowsheets.full_treatment_train.flowsheet_components import (
feed_block,
)
from watertap.examples.flowsheets.full_treatment_train.model_components import (
unit_separator,
unit_0DRO,
unit_1DRO,
property_models,
)
from watertap.examples.flowsheets.full_treatment_train.flowsheet_components.desalination import (
build_desalination,
solve_desalination,
scale_desalination,
initialize_desalination,
display_desalination,
)
import idaes.core.util.scaling as iscale
[docs]def build_costing(m, costing_package=WaterTAPCosting, **kwargs):
"""Add costing to a given flowsheet
Args:
m: model
costing_package : FlowsheetCostingBlock
"""
# call get_costing for each unit model
m.fs.costing = costing_package()
# the full_treatment_train uses a lower than default value
# for factor_maintenance_labor_chemical
m.fs.costing.factor_maintenance_labor_chemical.fix(0.02)
crf = m.fs.costing.factor_capital_annualization
# Nanofiltration
if hasattr(m.fs, "NF"):
if kwargs["NF_type"] == "ZO":
m.fs.NF.costing = UnitModelCostingBlock(default={
"flowsheet_costing_block":m.fs.costing,
})
elif kwargs["NF_type"] == "Sep":
raise NotImplementedError(
"get_costing will not be implemented for the NF separator model."
)
if hasattr(m.fs, "pump_NF"):
m.fs.pump_NF.costing = UnitModelCostingBlock(default={
"flowsheet_costing_block":m.fs.costing,
"costing_method_arguments":{"pump_type":PumpType.low_pressure},
})
m.fs.costing.cost_flow(pyunits.convert(m.fs.pump_NF.work_mechanical[0], to_units=pyunits.kW), "electricity")
# Reverse Osmosis
if hasattr(m.fs, "RO"):
if kwargs["RO_type"] == "0D" or kwargs["RO_type"] == "1D":
m.fs.RO.costing = UnitModelCostingBlock(default={
"flowsheet_costing_block":m.fs.costing,
})
elif kwargs["RO_type"] == "Sep":
raise NotImplementedError(
"get_costing will not be implemented for the RO separator model."
)
# Stage 2 RO
if hasattr(m.fs, "RO2"):
m.fs.RO2.costing = UnitModelCostingBlock(default={
"flowsheet_costing_block":m.fs.costing,
"costing_method_arguments":{"ro_type":ROType.high_pressure},
})
# Pump
if hasattr(m.fs, "pump_RO"):
m.fs.pump_RO.costing = UnitModelCostingBlock(default={
"flowsheet_costing_block":m.fs.costing,
"costing_method_arguments":{"pump_type":PumpType.high_pressure},
})
m.fs.costing.cost_flow(pyunits.convert(m.fs.pump_RO.work_mechanical[0], to_units=pyunits.kW), "electricity")
# Stage 2 pump
if hasattr(m.fs, "pump_RO2"):
m.fs.pump_RO2.costing = UnitModelCostingBlock(default={
"flowsheet_costing_block":m.fs.costing,
"costing_method_arguments":{"pump_type":PumpType.high_pressure},
})
m.fs.costing.cost_flow(pyunits.convert(m.fs.pump_RO2.work_mechanical[0], to_units=pyunits.kW), "electricity")
# ERD
if hasattr(m.fs, "ERD"):
m.fs.ERD.costing = UnitModelCostingBlock(default={
"flowsheet_costing_block":m.fs.costing,
"costing_method_arguments":{"pump_type":PumpType.pressure_exchanger},
})
m.fs.costing.cost_flow(pyunits.convert(m.fs.ERD.work_mechanical[0], to_units=pyunits.kW), "electricity")
# Pretreatment
if hasattr(m.fs, "stoich_softening_mixer_unit"):
m.fs.stoich_softening_mixer_unit.costing = UnitModelCostingBlock(default={
"flowsheet_costing_block":m.fs.costing,
"costing_method_arguments":{"mixer_type":MixerType.CaOH2},
})
m.fs.costing.cost_flow(m.fs.stoich_softening_mixer_unit.lime_stream.flow_mol[0] * \
m.fs.stoich_softening_mixer_unit.lime_stream.mole_frac_comp[0, "Ca(OH)2"], "CaOH2")
# Post-treatment
if hasattr(m.fs, "ideal_naocl_mixer_unit"):
# print('FOUND CHLORINATION UNIT')
m.fs.ideal_naocl_mixer_unit.costing = UnitModelCostingBlock(default={
"flowsheet_costing_block":m.fs.costing,
"costing_method_arguments":{"mixer_type":MixerType.NaOCl},
})
m.fs.costing.cost_flow(m.fs.ideal_naocl_mixer_unit.naocl_stream.flow_mol[0] *\
m.fs.ideal_naocl_mixer_unit.naocl_stream.mole_frac_comp[0, "OCl_-"], "NaOCl")
if hasattr(m.fs, "mixer_permeate"):
m.fs.mixer_permeate.costing = UnitModelCostingBlock(default={
"flowsheet_costing_block":m.fs.costing,
"costing_method_arguments":{"mixer_type":MixerType.default},
})
# call get_system_costing for whole flowsheet
m.fs.costing.cost_process()
m.fs.costing.add_LCOW(m.fs.treated_flow_vol)
if hasattr(m.fs, "stoich_softening_mixer_unit"):
m.fs.lime_softening_unit_capex = Expression(
expr=m.fs.stoich_softening_mixer_unit.costing.capital_cost
/ m.fs.costing.annual_water_production
* crf
)
else:
m.fs.lime_softening_unit_capex = Expression(expr=0)
if hasattr(m.fs, "ideal_naocl_mixer_unit"):
m.fs.chlorination_unit_capex = Expression(
expr=m.fs.ideal_naocl_mixer_unit.costing.capital_cost
/ m.fs.costing.annual_water_production
* crf
)
else:
m.fs.chlorination_unit_capex = Expression(expr=0)
# apply scaling to cost variables and constraints
scale_costing(m)
def scale_costing(self):
for b_unit in self.component_objects(Block, descend_into=True):
if hasattr(b_unit, "costing"):
base = b_unit.costing
for var in base.component_objects(Var):
if iscale.get_scaling_factor(var) is None:
iscale.set_scaling_factor(var, 1e-3)
for con in base.component_data_objects(Constraint):
iscale.constraint_scaling_transform(con, 1e-3)
def display_costing(m):
crf = m.fs.costing.factor_capital_annualization
if not hasattr(m.fs, "pump_RO2"):
m.fs.pump_RO2 = Block()
m.fs.pump_RO2.costing = Block()
m.fs.pump_RO2.costing.fixed_operating_cost = Param(initialize=0)
if not hasattr(m.fs, "NF"):
m.fs.NF = Block()
m.fs.NF.costing = Block()
m.fs.NF.costing.fixed_operating_cost = Param(initialize=0)
if not hasattr(m.fs, "RO2"):
m.fs.RO2 = Block()
m.fs.RO2.costing = Block()
m.fs.RO2.costing.fixed_operating_cost = Param(initialize=0)
# UNITS FOR ALL COST COMPONENTS [=] $/m3 of permeate water produced
cost_dict = {
"LCOW": m.fs.costing.LCOW, # Total LCOW
"Total CAPEX": m.fs.costing.total_investment_cost
* crf
/ m.fs.costing.annual_water_production, # Direct + Indirect CAPEX
"Direct CAPEX": m.fs.costing.total_capital_cost
* crf
/ m.fs.costing.annual_water_production, # Direct CAPEX for all system components
"Indirect CAPEX": (
m.fs.costing.total_investment_cost - m.fs.costing.total_capital_cost
)
* crf
/ m.fs.costing.annual_water_production, # Indirect CAPEX for miscellaneous items
"Total OPEX": m.fs.costing.total_operating_cost
/ m.fs.costing.annual_water_production, # Total OPEX
"Labor & Maintenance Costs": m.fs.costing.maintenance_labor_chemical_operating_cost
/ m.fs.costing.annual_water_production,
"Total Electricity Cost": m.fs.costing.aggregate_flow_costs["electricity"]
/ m.fs.costing.annual_water_production,
"Total Membrane Replacement Cost": (
m.fs.NF.costing.fixed_operating_cost
+ m.fs.RO.costing.fixed_operating_cost
+ m.fs.RO2.costing.fixed_operating_cost
)
/ m.fs.costing.annual_water_production,
"Lime softener CAPEX": m.fs.lime_softening_unit_capex,
"Lime softener OPEX": m.fs.costing.aggregate_flow_costs["CaOH2"]/m.fs.costing.annual_water_production,
"Chlorination CAPEX": m.fs.chlorination_unit_capex,
"Chlorination OPEX": m.fs.costing.aggregate_flow_costs["NaOCl"]/m.fs.costing.annual_water_production,
}
for item, val in cost_dict.items():
print(f"{item} = {value(val)}")
return cost_dict