Skip to content

Commit

Permalink
Dye Desalination Parameter Update (#1126)
Browse files Browse the repository at this point in the history
* Add option to add/remove pretreatment in dye desal with RO

* Adjust default parameters based on project team's suggestions

* Update test files

* Add more scaling to dye desal with RO

* Address test failures associated with the GUI & nanofiltration

* Update dye desal sweep analysis

* Resolve test_dye_sweep failure

* Address pylint errors

* Readthedocs fix

* Expand code coverage

* Update dye desal GUI

* Minor clean-up

* Fix GUI test failure

* Add config option for dye revenue/disposal cost

* Update sweep analyses

* Fix test_dye_sweep

* Print additional cost metrics

* Change dye_revenue from a mass basis to a volumetric basis

* Change dye_sweep plots from LCOW to LCOT

* Update sweep analyses

* Address some of Adam's suggestions

* Replace include_pretreatment boolean with hasattr

* Refine pretreatment initialization

* Make salt rejection = 0.05 and dye rejection = 0.99

* Update dye disposal cost and water revenue values

* Replace brine disposal cost (/m3) with brine revenue (/m3) in NF flowsheet

* Update tests

* Update sweep analyses

* Update sweep analysis

* Change dye & salt disposal costs from a vol basis to a mass basis

* Update sweep analysis

* Revert back to volumetric basis

* Update sweep analysis

* Delete visualization tools

* Address pylint issues

* Update GUI

* Add hasattr's into GUI for pretreatment

* Update GUI image

* Remove has_dye_revenue config option and clean up files

* Move dye disposal cost from revenue section to operating costs section
  • Loading branch information
MarcusHolly authored Sep 29, 2023
1 parent 3b75723 commit 3744d2c
Show file tree
Hide file tree
Showing 11 changed files with 568 additions and 245 deletions.
14 changes: 7 additions & 7 deletions watertap/data/techno_economic/nanofiltration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,29 +108,29 @@ rHGO_dye_rejection:
cost_method: cost_membrane
cost_factor: None
membrane_cost:
value: 15
value: 50
units: USD_2018/m^2
membrane_replacement_rate:
value: 0.15
value: 0.2
units: 1/year
recovery_frac_mass_H2O:
value: 0.75
value: 0.83
units: dimensionless
default_removal_frac_mass_comp:
value: 0
units: dimensionless
removal_frac_mass_comp:
dye:
value: 0.9839
value: 0.9917
units: dimensionless
constituent_longform: Dye
tds:
value: 0.2655
value: 0.2117357
units: dimensionless
constituent_longform: Total Suspended Solids (TSS)
applied_pressure:
value: 6.895 # corresponds to default of 100 psi
value: 7 # corresponds to default of 100 psi
units: bar
water_permeability_coefficient:
value: 100
value: 105
units: liters/m^2/hour/bar
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ def build():
def set_operating_conditions(m):
dye_sep = m.fs.dye_separation
# feed
flow_vol = 120 / 3600 * pyunits.m**3 / pyunits.s
conc_mass_dye = 2.5 * pyunits.kg / pyunits.m**3
conc_mass_tds = 50.0 * pyunits.kg / pyunits.m**3
flow_vol = 280 / 3600 * pyunits.m**3 / pyunits.s
conc_mass_dye = 0.2 * pyunits.kg / pyunits.m**3
conc_mass_tds = 2 * pyunits.kg / pyunits.m**3

m.fs.feed.flow_vol[0].fix(flow_vol)
m.fs.feed.conc_mass_comp[0, "dye"].fix(conc_mass_dye)
Expand All @@ -128,6 +128,7 @@ def set_operating_conditions(m):
dye_sep.P1.applied_pressure.fix(
dye_sep.nanofiltration.applied_pressure.get_values()[0]
)
dye_sep.P1.eta_pump.fix(0.75) # pump efficiency [-]
dye_sep.P1.lift_height.unfix()

return
Expand Down Expand Up @@ -165,35 +166,36 @@ def add_costing(m):
# create costing blocks
dye_sep.nanofiltration.costing = UnitModelCostingBlock(**costing_kwargs)
dye_sep.P1.costing = UnitModelCostingBlock(**costing_kwargs)
m.fs.zo_costing.pump_electricity.pump_cost["default"].fix(76)

# aggregate unit level costs
m.fs.zo_costing.cost_process()

# create system level cost metrics
m.fs.brine_disposal_cost = Expression(
m.fs.brine_recovery_revenue = Expression(
expr=(
m.fs.zo_costing.utilization_factor
* (
m.fs.zo_costing.waste_disposal_cost
m.fs.zo_costing.brine_recovery_cost
* pyunits.convert(
m.fs.permeate.properties[0].flow_vol,
to_units=pyunits.m**3 / m.fs.zo_costing.base_period,
)
)
),
doc="Cost of disposing of saline brine/ NF permeate",
doc="Revenue from recovering saline brine/ NF permeate",
)

m.fs.dye_recovery_revenue = Expression(
m.fs.dye_disposal_cost = Expression(
expr=(
m.fs.zo_costing.utilization_factor
* m.fs.zo_costing.dye_mass_cost
* m.fs.zo_costing.dye_disposal_cost
* pyunits.convert(
m.fs.dye_retentate.flow_mass_comp[0, "dye"],
to_units=pyunits.kg / m.fs.zo_costing.base_period,
m.fs.dye_retentate.properties[0].flow_vol,
to_units=pyunits.m**3 / m.fs.zo_costing.base_period,
)
),
doc="Savings from dye-retentate recovered back to the plant",
doc="Cost of disposing of dye waste",
)

# combine results for system level costs - to be the same syntax as dye_desalination_withRO
Expand All @@ -213,10 +215,10 @@ def total_operating_cost(b):
to_units=pyunits.USD_2020 / pyunits.year,
)

@m.fs.Expression(doc="Total cost of dye recovered and brine disposed")
@m.fs.Expression(doc="Total cost of brine recovery and dye disposal")
def total_externalities(b):
return pyunits.convert(
b.dye_recovery_revenue - b.brine_disposal_cost,
m.fs.brine_recovery_revenue - m.fs.dye_disposal_cost,
to_units=pyunits.USD_2020 / pyunits.year,
)

Expand Down Expand Up @@ -275,6 +277,19 @@ def display_costing(m):
m.fs.total_externalities, to_units=pyunits.MUSD_2020 / pyunits.year
)
)
brr = value(
pyunits.convert(
m.fs.brine_recovery_revenue, to_units=pyunits.USD_2020 / pyunits.year
)
)
print(f"Brine recovery revenue: {brr: .4f} M$/year")
ddc = value(
pyunits.convert(
m.fs.dye_disposal_cost, to_units=pyunits.USD_2020 / pyunits.year
)
)
print(f"Dye disposal cost: {ddc: .2f} $/year")
print(f"Brine recovery revenue: {brr: .2f} $/year")
print(f"Total Externalities: {total_externalities:.4f} M$/year")

levelized_cost_treatment = value(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ defined_flows:
waste_disposal:
value: 1
units: USD_2020/m**3
dye_mass:
value: 0.1
units: USD_2020/kg
dye_disposal:
value: 120
units: USD_2020/m**3
brine_recovery:
value: 1
units: USD_2020/m**3
recovered_water:
value: 0.1
value: 1.25
units: USD_2020/m**3
global_parameters:
plant_lifetime:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ def export_variables(flowsheet=None, exports=None):
exports.add(
obj=fs.feed.conc_mass_comp[0, "dye"],
name="Dye concentration",
ui_units=pyunits.g / pyunits.L,
display_units="g/L", # can this be done by default?
ui_units=pyunits.kg / pyunits.m**3,
display_units="kg/m3",
rounding=2,
description="Inlet dye concentration",
is_input=True,
Expand All @@ -62,8 +62,8 @@ def export_variables(flowsheet=None, exports=None):
exports.add(
obj=fs.feed.conc_mass_comp[0, "tds"],
name="TDS concentration",
ui_units=pyunits.g / pyunits.L,
display_units="g/L", # can this be done by default?
ui_units=pyunits.kg / pyunits.m**3,
display_units="kg/m3",
rounding=2,
description="Inlet total dissolved solids (TDS) concentration",
is_input=True,
Expand Down Expand Up @@ -164,17 +164,20 @@ def export_variables(flowsheet=None, exports=None):
)

# Unit model, secondary WWTP
exports.add(
obj=fs.pretreatment.wwtp.energy_electric_flow_vol_inlet,
name="Specific energy consumption per inlet flow rate",
ui_units=pyunits.kWh / pyunits.m**3,
display_units="kWh/m3",
rounding=2,
description="Electrical energy consumption with respect to influent volumetric flow rate",
is_input=True,
input_category="Secondary Wastewater Treatment",
is_output=False,
)
if hasattr(fs, "pretreatment"):
exports.add(
obj=fs.pretreatment.wwtp.energy_electric_flow_vol_inlet,
name="Specific energy consumption per inlet flow rate",
ui_units=pyunits.kWh / pyunits.m**3,
display_units="kWh/m3",
rounding=2,
description="Electrical energy consumption with respect to influent volumetric flow rate",
is_input=True,
input_category="Secondary Wastewater Treatment",
is_output=False,
)
else:
pass

# Unit model, RO
v = fs.desalination.RO.A_comp
Expand Down Expand Up @@ -338,10 +341,10 @@ def export_variables(flowsheet=None, exports=None):
input_category="System Costs",
is_output=False,
)
v = fs.zo_costing.dye_mass_cost
v = fs.zo_costing.dye_disposal_cost
exports.add(
obj=v,
name="Value of recovered dye",
name="Dye disposal cost per volume",
ui_units=getattr(pyunits, str(v._units)),
display_units=str(v._units),
rounding=2,
Expand Down Expand Up @@ -599,18 +602,21 @@ def export_variables(flowsheet=None, exports=None):
is_output=True,
output_category="Capital costs",
)
wwtp_capex = fs.pretreatment.wwtp.costing.capital_cost
exports.add(
obj=wwtp_capex,
name="Secondary WWTP Cost",
ui_units=fs.zo_costing.base_currency,
display_units="$",
rounding=2,
description="Secondary WWTP representative of conventional activated sludge with secondary clarifier",
is_input=False,
is_output=True,
output_category="Capital costs",
)
if hasattr(fs, "pretreatment"):
wwtp_capex = fs.pretreatment.wwtp.costing.capital_cost
exports.add(
obj=wwtp_capex,
name="Secondary WWTP Cost",
ui_units=fs.zo_costing.base_currency,
display_units="$",
rounding=2,
description="Secondary WWTP representative of conventional activated sludge with secondary clarifier",
is_input=False,
is_output=True,
output_category="Capital costs",
)
else:
pass
nf_capex = (
fs.dye_separation.nanofiltration.costing.capital_cost
+ fs.dye_separation.P1.costing.capital_cost
Expand Down Expand Up @@ -676,40 +682,31 @@ def export_variables(flowsheet=None, exports=None):
output_category="Operating costs",
)
exports.add(
obj=fs.sludge_disposal_cost,
name="WWTP sludge disposal",
obj=fs.dye_disposal_cost,
name="Dye disposal",
ui_units=fs.zo_costing.base_currency / pyunits.year,
display_units="$/year",
rounding=2,
description="Annual sludge disposal",
description="Annual dye disposal",
is_input=False,
is_output=True,
output_category="Operating costs",
)
if hasattr(fs, "pretreatment"):
exports.add(
obj=fs.sludge_disposal_cost,
name="WWTP sludge disposal",
ui_units=fs.zo_costing.base_currency / pyunits.year,
display_units="$/year",
rounding=2,
description="Annual sludge disposal",
is_input=False,
is_output=True,
output_category="Operating costs",
)
else:
pass
# Revenue
total_revenue = fs.water_recovery_revenue + fs.dye_recovery_revenue
exports.add(
obj=total_revenue,
name="Total",
ui_units=fs.zo_costing.base_currency / pyunits.year,
display_units="$/year",
rounding=2,
description="Total revenue - including the value of recovered dye and water",
is_input=False,
is_output=True,
output_category="Revenue",
)
exports.add(
obj=fs.dye_recovery_revenue,
name="Dye",
ui_units=fs.zo_costing.base_currency / pyunits.year,
display_units="$/year",
rounding=2,
description="Revenue from selling dye",
is_input=False,
is_output=True,
output_category="Revenue",
)
exports.add(
obj=fs.water_recovery_revenue,
name="Water",
Expand All @@ -725,7 +722,7 @@ def export_variables(flowsheet=None, exports=None):

def build_flowsheet():
# build and solve initial flowsheet
m = build()
m = build(include_pretreatment=False)

set_operating_conditions(m)
assert_degrees_of_freedom(m, 0)
Expand Down
Loading

0 comments on commit 3744d2c

Please sign in to comment.