Skip to content

Commit

Permalink
Add Costing for Dewatering Unit (watertap-org#1212)
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-a-a authored Nov 22, 2023
1 parent e1a2a82 commit 1d22d6f
Show file tree
Hide file tree
Showing 7 changed files with 677 additions and 13 deletions.
30 changes: 23 additions & 7 deletions tutorials/BSM2.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,12 @@
"metadata": {},
"outputs": [],
"source": [
"\n",
"import pyomo.environ as pyo\n",
"from pyomo.network import Arc, SequentialDecomposition\n",
"from idaes.core import FlowsheetBlock\n",
"import idaes.logger as idaeslog\n",
"from idaes.core.solvers import get_solver\n",
"import idaes.core.util.scaling as iscale\n",
"\n",
"\n"
"import idaes.core.util.scaling as iscale"
]
},
{
Expand Down Expand Up @@ -119,7 +116,7 @@
"source": [
"### Step 1.3: Import all BSM2 required property models\n",
"\n",
"Property block are an important building block in WaterTap as they are a Python class which contain information on units, physical properties, etc."
"Property blocks are an important building block in WaterTap as they are a Python class which contain information on units, physical properties, etc."
]
},
{
Expand Down Expand Up @@ -183,7 +180,7 @@
"id": "ac6e041d-ee0a-41e7-a194-6db689b2e92c",
"metadata": {},
"source": [
"We then include all the necessary property blocks we imported into the flowsheet"
"We then include all the necessary property blocks we imported into the flowsheet. Namely, we include the ASM1 and ADM1 models, which are separated into their respective property and reaction models. Additionally, the vapor phase of ADM1 was separated into its own property model."
]
},
{
Expand All @@ -209,7 +206,7 @@
"\n",
"We will start by setting up the activated sludge process unit models and connectivity.\n",
"\n",
"First, we set up a Feed model for our feed stream called Feedwater "
"First, we set up a Feed model for our feed stream and will name it `Feedwater`. "
]
},
{
Expand Down Expand Up @@ -618,6 +615,25 @@
"m.fs.RADM.liquid_outlet.temperature.fix(308.15)"
]
},
{
"cell_type": "markdown",
"id": "58558077",
"metadata": {},
"source": [
"Additionally, the dewatering unit includes an equation relating its hydraulic retention time to its volume and influent flowrate. We can choose to specify hydraulic retention time or the unit's volume to satisfy 0 degrees of freedom."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "982691fd",
"metadata": {},
"outputs": [],
"source": [
"# Dewatering unit\n",
"m.fs.DU.hydraulic_retention_time.fix(1800 * pyo.units.s)"
]
},
{
"cell_type": "markdown",
"id": "663b9e78-c1b3-41b5-b9d1-fe1fc5acd274",
Expand Down
194 changes: 194 additions & 0 deletions watertap/costing/unit_models/dewatering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#################################################################################
# WaterTAP Copyright (c) 2020-2023, 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/"
#################################################################################

import pyomo.environ as pyo
from ..util import (
register_costing_parameter_block,
make_capital_cost_var,
)
from idaes.core.util.misc import StrEnum
from idaes.core.util.exceptions import ConfigurationError

"""
Ref: W. McGivney, S. Kawamura, Cost estimating manual for water treatment facilities, John Wiley & Sons, 2008. http://onlinelibrary.wiley.com/book/10.1002/9780470260036.
"""


class DewateringType(StrEnum):
filter_belt_press = "filter_belt_press"
filter_plate_press = "filter_plate_press"
centrifuge = "centrifuge"


def cost_dewatering(
blk, dewatering_type=DewateringType.centrifuge, cost_electricity_flow=True
):

if dewatering_type == DewateringType.centrifuge:
cost_centrifuge(blk, dewatering_type, cost_electricity_flow)

elif dewatering_type == DewateringType.filter_belt_press:
cost_filter_belt_press(blk, dewatering_type, cost_electricity_flow)

elif dewatering_type == DewateringType.filter_plate_press:
cost_filter_plate_press(blk, dewatering_type, cost_electricity_flow)
else:
raise ConfigurationError(
f"{blk.unit_model.name} received invalid argument for dewatering_type:"
f" {dewatering_type}. Argument must be a member of the DewateringType Enum class."
)


def build_centrifuge_cost_param_block(blk):
# NOTE: costing data are from McGivney & Kawamura, 2008
blk.capital_a_parameter = pyo.Var(
initialize=328.03,
doc="A parameter for capital cost",
units=pyo.units.USD_2007 / (pyo.units.gallon / pyo.units.hour),
)
blk.capital_b_parameter = pyo.Var(
initialize=751295,
doc="B parameter for capital cost",
units=pyo.units.USD_2007,
)


def build_filter_belt_press_cost_param_block(blk):
# NOTE: costing data are from McGivney & Kawamura, 2008
blk.capital_a_parameter = pyo.Var(
initialize=146.29,
doc="A parameter for capital cost",
units=pyo.units.USD_2007 / (pyo.units.gallon / pyo.units.hour),
)
blk.capital_b_parameter = pyo.Var(
initialize=433972,
doc="B parameter for capital cost",
units=pyo.units.USD_2007,
)


def build_filter_plate_press_cost_param_block(blk):
# NOTE: costing data are from McGivney & Kawamura, 2008
blk.capital_a_parameter = pyo.Var(
initialize=102794,
doc="A parameter for capital cost",
units=pyo.units.USD_2007 / (pyo.units.gallon / pyo.units.hour),
)
blk.capital_b_parameter = pyo.Var(
initialize=0.4216,
doc="B parameter for capital cost",
units=pyo.units.dimensionless,
)


@register_costing_parameter_block(
build_rule=build_centrifuge_cost_param_block,
parameter_block_name="centrifuge",
)
def cost_centrifuge(
blk, dewatering_type=DewateringType.centrifuge, cost_electricity_flow=True
):
"""
Centrifuge costing method
"""
make_capital_cost_var(blk)
cost_blk = blk.costing_package.centrifuge
t0 = blk.flowsheet().time.first()
x = flow_in = pyo.units.convert(
blk.unit_model.inlet.flow_vol[t0], to_units=pyo.units.gallon / pyo.units.hr
)
blk.capital_cost_constraint = pyo.Constraint(
expr=blk.capital_cost
== pyo.units.convert(
cost_blk.capital_a_parameter * x + cost_blk.capital_b_parameter,
to_units=blk.costing_package.base_currency,
)
)
if cost_electricity_flow:
blk.costing_package.cost_flow(
pyo.units.convert(
blk.unit_model.electricity_consumption[t0],
to_units=pyo.units.kW,
),
"electricity",
)


@register_costing_parameter_block(
build_rule=build_filter_belt_press_cost_param_block,
parameter_block_name="filter_belt_press",
)
def cost_filter_belt_press(
blk, dewatering_type=DewateringType.filter_belt_press, cost_electricity_flow=True
):
"""
Belt Press Filter costing method
"""
make_capital_cost_var(blk)
cost_blk = blk.costing_package.filter_belt_press
t0 = blk.flowsheet().time.first()
x = flow_in = pyo.units.convert(
blk.unit_model.inlet.flow_vol[t0], to_units=pyo.units.gallon / pyo.units.hr
)

blk.capital_cost_constraint = pyo.Constraint(
expr=blk.capital_cost
== pyo.units.convert(
cost_blk.capital_a_parameter * x + cost_blk.capital_b_parameter,
to_units=blk.costing_package.base_currency,
)
)
if cost_electricity_flow:
blk.costing_package.cost_flow(
pyo.units.convert(
blk.unit_model.electricity_consumption[t0],
to_units=pyo.units.kW,
),
"electricity",
)


@register_costing_parameter_block(
build_rule=build_filter_plate_press_cost_param_block,
parameter_block_name="filter_plate_press",
)
def cost_filter_plate_press(
blk, dewatering_type=DewateringType.filter_plate_press, cost_electricity_flow=True
):
"""
Plate Press Filter costing method
"""
make_capital_cost_var(blk)

cost_blk = blk.costing_package.filter_plate_press
t0 = blk.flowsheet().time.first()
x_units = pyo.units.gallon / pyo.units.hr
x = flow_in = pyo.units.convert(blk.unit_model.inlet.flow_vol[t0], to_units=x_units)

blk.capital_cost_constraint = pyo.Constraint(
expr=blk.capital_cost
== pyo.units.convert(
cost_blk.capital_a_parameter
* x_units
* (x / x_units) ** cost_blk.capital_b_parameter,
to_units=blk.costing_package.base_currency,
)
)

if cost_electricity_flow:
blk.costing_package.cost_flow(
pyo.units.convert(
blk.unit_model.electricity_consumption[t0],
to_units=pyo.units.kW,
),
"electricity",
)
2 changes: 1 addition & 1 deletion watertap/data/techno_economic/centrifuge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ default:
HRT: # Hydraulic retention time
value: 0.5
units: hr
sizing_cost:
sizing_cost: # TODO: Update to 430892.24 based on cost curve values in watertap/unit_models/tests/test_dewatering_unit? 1 $/m3 is too unrealistic.
value: 1
units: USD_2020/m^3
recovery_frac_mass_H2O:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,9 @@ def set_operating_conditions(m):
m.fs.RADM.volume_vapor.fix(300)
m.fs.RADM.liquid_outlet.temperature.fix(308.15)

# Dewatering Unit - fix either HRT or volume.
m.fs.DU.hydraulic_retention_time.fix(1800 * pyo.units.s)


def initialize_system(m):
# Initialize flowsheet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ def export_variables(flowsheet=None, exports=None):
is_output=False,
)

# TODO: uncomment and revise below once costing is merged
# System costing
# exports.add(
# obj=fs.costing.utilization_factor,
Expand Down Expand Up @@ -2858,6 +2859,7 @@ def build_flowsheet():
results = solve(m)
assert_optimal_termination(results)

# TODO: incorporate costing when merged
# add_costing(m)
# assert_degrees_of_freedom(m, 0)
# m.fs.costing.initialize()
Expand Down
61 changes: 60 additions & 1 deletion watertap/unit_models/dewatering.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@
from pyomo.environ import (
Param,
units as pyunits,
Var,
NonNegativeReals,
)
from pyomo.common.config import ConfigValue, In

from idaes.core.util.exceptions import (
ConfigurationError,
)
from watertap.costing.unit_models.dewatering import cost_dewatering

__author__ = "Alejandro Garciadiego, Adam Atia"

Expand Down Expand Up @@ -128,6 +131,51 @@ def build(self):
doc="Percentage of suspended solids removed",
)

self.electricity_consumption = Var(
self.flowsheet().time,
units=pyunits.kW,
bounds=(0, None),
doc="Electricity consumption of unit",
)

# 0.026 kWh/m3 average between averages of belt and screw presses & centrifuge in relation to flow capacity
self.energy_electric_flow_vol_inlet = Param(
self.flowsheet().time,
units=pyunits.kWh / (pyunits.m**3),
initialize=0.026,
mutable=True,
doc="Specific electricity intensity of unit",
)

@self.Constraint(self.flowsheet().time, doc="Electricity consumption equation")
def eq_electricity_consumption(blk, t):
return blk.electricity_consumption[t] == pyunits.convert(
blk.energy_electric_flow_vol_inlet[t] * blk.inlet.flow_vol[t],
to_units=pyunits.kW,
)

self.hydraulic_retention_time = Var(
self.flowsheet().time,
initialize=1800,
domain=NonNegativeReals,
units=pyunits.s,
doc="Hydraulic retention time",
)
self.volume = Var(
self.flowsheet().time,
initialize=1800,
domain=NonNegativeReals,
units=pyunits.m**3,
doc="Hydraulic retention time",
)

@self.Constraint(self.flowsheet().time, doc="Hydraulic retention time equation")
def eq_hydraulic_retention(blk, t):
return (
self.hydraulic_retention_time[t]
== self.volume[t] / self.inlet.flow_vol[t]
)

@self.Expression(self.flowsheet().time, doc="Suspended solid concentration")
def TSS_in(blk, t):
if blk.config.activated_sludge_model == ActivatedSludgeModelType.ASM1:
Expand Down Expand Up @@ -177,10 +225,17 @@ def non_particulate_components(blk, t, i):

def _get_performance_contents(self, time_point=0):
var_dict = {}
param_dict = {}
for k in self.split_fraction.keys():
if k[0] == time_point:
var_dict[f"Split Fraction [{str(k[1:])}]"] = self.split_fraction[k]
return {"vars": var_dict}
var_dict["Electricity consumption"] = self.electricity_consumption[time_point]
param_dict[
"Specific electricity consumption"
] = self.energy_electric_flow_vol_inlet[time_point]
var_dict["Unit Volume"] = self.volume[time_point]
var_dict["Hydraulic Retention Time"] = self.hydraulic_retention_time[time_point]
return {"vars": var_dict, "params": param_dict}

def _get_stream_table_contents(self, time_point=0):
outlet_list = self.create_outlet_list()
Expand All @@ -195,3 +250,7 @@ def _get_stream_table_contents(self, time_point=0):
io_dict[o] = getattr(self, o + "_state")

return create_stream_table_dataframe(io_dict, time_point=time_point)

@property
def default_costing_method(self):
return cost_dewatering
Loading

0 comments on commit 1d22d6f

Please sign in to comment.