WaterTAP Costing Framework
The WaterTAP Costing Base class and utility functions contain extensions, methods, and variables and constraints common to all WaterTAP Costing Packages, and which would be useful for creating custom costing packages for WaterTAP.
Extensions Over IDAES Costing Framework
The WaterTAP Costing Framework extends the functionality of the IDAES Process Costing Framework in several ways:
Unit models can self-register a default costing method by specifying a default_costing_method attribute. This allows the costing method(s) to be specified with the unit model definition.
import pyomo.environ as pyo
import idaes.core as idc
from watertap.costing import WaterTAPCosting
def cost_unit_model(blk):
blk.capital_cost = pyo.Var(
initialize=1,
units=blk.config.flowsheet_costing_block.base_currency,
bounds=(0, None),
doc="Capital cost of unit operation",
)
@idc.declare_process_block_class("MyUnitModel")
class MyUnitModelData(idc.UnitModelBlockData):
@property
def default_costing_method(self):
# could point to a static method on
# this class, could be function in
# a different module even
return cost_unit_model
m = pyo.ConcreteModel()
m.fs = idc.FlowsheetBlock(dynamic=False)
m.fs.costing = WaterTAPCosting()
m.fs.my_unit = MyUnitModel()
# the `default_costing_method_attribute` on the
# unit model is checked, and the function
# `cost_unit_model` returned then build the costing block
m.fs.my_unit.costing = idc.UnitModelCostingBlock(
flowsheet_costing_block=m.fs.costing,
)
The method register_flow_type will create a new Expression if a costing component is not already defined and the costing component is not constant. The default behavior in IDAES is to always create a new Var. This allows the user to specify intermediate values in register_flow_type.
import pyomo.environ as pyo
from idaes.core import FlowsheetBlock
from watertap.costing import WaterTAPCosting
m = pyo.ConcreteModel()
m.fs = FlowsheetBlock(dynamic=False)
m.fs.costing = WaterTAPCosting()
m.fs.naocl_bulk_cost = pyo.Param(
mutable=True,
initialize=0.23,
doc="NaOCl cost",
units=pyo.units.USD_2018 / pyo.units.kg,
)
m.fs.naocl_purity = pyo.Param(
mutable=True,
initialize=0.15,
doc="NaOCl purity",
units=pyo.units.dimensionless,
)
# This will create an Expression m.fs.costing.naocl_cost whose expr is the second argument
# so changes to m.fs.naocl_bulk_cost and m.fs.naocl_purity will affect the underlying
# new Expression m.fs.costing.naocl_cost.
m.fs.costing.register_flow_type("naocl", m.fs.naocl_bulk_cost / m.fs.naocl_purity)
# This, however, will create a Var called m.fs.costing.caoh2_cost whose *value* is the second argument
m.fs.costing.register_flow_type("caoh2", 0.12 * pyo.units.USD_2018 / pyo.units.kg)
Unit models specify one of the global indirect capital cost multipliers, either TIC or TPEC (defined below) when defining their capital costs. The costing package will then aggregate both direct and total capital costs.
import pyomo.environ as pyo
import idaes.core as idc
from watertap.costing import WaterTAPCosting
def cost_my_unit_model(blk):
blk.capital_cost = pyo.Var(
initialize=1,
units=blk.config.flowsheet_costing_block.base_currency,
bounds=(0, None),
doc="Capital cost of unit operation",
)
# Adds blk.cost_factor, an expression pointing
# to the appropriate indirect capital cost adder
# and blk.direct_capital_cost, which is a expression
# defined to be blk.capital_cost / blk.cost_factor.
# Valid strings are "TIC" and "TPEC", all others
# will result in an indirect capital cost factor
# of 1.
blk.costing_package.add_cost_factor(blk, "TIC")
blk.capital_cost_constraint = pyo.Constraint(
expr=blk.capital_cost
== blk.cost_factor * ( 42 * pyo.units.USD_2018 )
)
@idc.declare_process_block_class("MyUnitModel")
class MyUnitModelData(idc.UnitModelBlockData):
pass
m = pyo.ConcreteModel()
m.fs = idc.FlowsheetBlock(dynamic=False)
m.fs.costing = WaterTAPCosting()
m.fs.my_unit = MyUnitModel()
m.fs.my_unit.costing = idc.UnitModelCostingBlock(
costing_method=cost_my_unit_model,
flowsheet_costing_block=m.fs.costing,
)
m.fs.my_unit.costing.initialize()
m.fs.my_unit.costing.cost_factor.pprint()
m.fs.my_unit.costing.capital_cost.pprint()
m.fs.my_unit.costing.direct_capital_cost.pprint()
cost_factor : Size=1, Index=None
Key : Expression
None : fs.costing.TIC
capital_cost : Capital cost of unit operation
Size=1, Index=None, Units=USD_2018
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : 0 : 84.0 : None : False : False : Reals
direct_capital_cost : Size=1, Index=None
Key : Expression
None : fs.my_unit.costing.capital_cost/fs.costing.TIC
A helper utility for defining global-level parameters specific to a unit model without changing the base costing package implementation.
import pyomo.environ as pyo
import idaes.core as idc
from watertap.costing import (
WaterTAPCosting,
register_costing_parameter_block,
make_capital_cost_var,
)
def build_my_unit_model_param_block(blk):
"""
This function builds the global parameters for MyUnitModel.
This function should also register needed flows using the
blk.parent_block().register_flow_type method on the costing package.
"""
blk.fixed_capital_cost = pyo.Var(
initialize=42,
doc="Fixed capital cost for all of my units",
units=pyo.units.USD_2020,
)
# This decorator ensures that the function
# `build_my_unit_model_param_block` is only
# added to the costing package once.
# It registers it as a sub-block with the
# name `my_unit`.
@register_costing_parameter_block(
build_rule=build_my_unit_model_param_block,
parameter_block_name="my_unit",
)
def cost_my_unit_model(blk):
"""
Cost an instance of MyUnitModel
"""
# creates the `capital_cost` Var
make_capital_cost_var(blk)
blk.costing_package.add_cost_factor(blk, "TIC")
# here we reference the `fixed_capital_cost` parameter
# automatically added by the `register_costing_parameter_block`
# decorator.
blk.capital_cost_constraint = pyo.Constraint(
expr=blk.capital_cost
== blk.cost_factor * blk.costing_package.my_unit.fixed_capital_cost
)
@idc.declare_process_block_class("MyUnitModel")
class MyUnitModelData(idc.UnitModelBlockData):
@property
def default_costing_method(self):
# could point to a static method on
# this class, could be function in
# a different module even
return cost_my_unit_model
m = pyo.ConcreteModel()
m.fs = idc.FlowsheetBlock(dynamic=False)
m.fs.costing = WaterTAPCosting()
m.fs.my_unit_1 = MyUnitModel()
# The `default_costing_method_attribute` on the
# unit model is checked, and the function
# `cost_my_unit_model` returned then build the costing block.
# This method also adds the `my_unit` global parameter block,
# so the global costing parameter m.fs.costing.my_unit.fixed_capital_cost
# is the same for all instances of MyUnitModel.
m.fs.my_unit_1.costing = idc.UnitModelCostingBlock(
flowsheet_costing_block=m.fs.costing,
)
m.fs.my_unit_2 = MyUnitModel()
# Here everythin as before, but the global parameter block
# m.fs.costing.my_unit is not re-built.
m.fs.my_unit_2.costing = idc.UnitModelCostingBlock(
flowsheet_costing_block=m.fs.costing,
)
Costing Index and Technoeconomic Factors
Default costing indices are provide with the WaterTAP Costing Framework, but the user is free to modify these for their needs.
WaterTAP uses the CE (Chemical Engineering) Cost Index to help account for the time-value of investments and are used in the capital and operating cost calculations. Unit process capital costs are adjusted to the year of the case study. The default year is 2018.
Common Global Costing Parameters
The build_global_params method builds common cost factor parameters necessary to calculate aggregates such as levelized cost of water (LCOW). Note that the default values can be overwritten in the derived class.
Cost factor |
Variable |
Name |
Default Value |
Description |
---|---|---|---|---|
Plant capacity utilization factor |
\(f_{util}\) |
|
90% |
Percentage of year plant is operating |
Electricity price |
\(P\) |
|
$0.07/kWh |
Electricity price in 2018 USD |
Electricity carbon intensity |
\(f_{eci}\) |
|
0.475 kg/kWh |
Carbon intensity of electricity |
Capital recovery factor |
\(f_{crf}\) |
|
10% |
Capital annualization (fraction of investment cost/year) |
Plant lifetime |
\(L\) |
|
30 years |
Plant lifetime |
Weighted average cost of capital |
\(f_{wacc}\) |
|
9.30734% |
Average cost of capital over plant lifetime |
Total purchased equipment cost (TPEC) |
\(f_{TPEC}\) |
|
4.121212 |
Common indirect capital cost multiplier for unit models |
Total installed cost (TIC) |
\(f_{TIC}\) |
|
2.0 |
Common indirect capital cost multiplier for unit models |
The relationship between the \(f_{crf}\), \(L\), and \(f_{wacc}\) is as follows:
\[f_{crf} = \frac{ f_{wacc} (1 + f_{wacc}) ^ L}{ (1 + f_{wacc}) ^ L - 1}\]
Therefore, at exactly two of the variables capital_recovery_factor
, plant_lifetime
and wacc
must be fixed. By default the variables plant_lifetime
and wacc
are fixed
and the variable capital_recovery_factor
is calculated.
The process-wide costs described below rely on two other factors which must be supplyed by the derived class, the total investment factor and the maintenance-labor-chemical factor.
Cost factor |
Variable |
Name |
Default Value |
Description |
---|---|---|---|---|
Total investment factor |
\(f_{toti}\) |
|
None |
Total investment factor (investment cost / equipment cost) |
Maintenance-labor-chemical factor |
\(f_{mlc}\) |
|
None |
Maintenance, labor, and chemical factor (fraction of equipment cost / year) |
Costing Process-Wide Costs
The WaterTAPCostingBlockData class includes variables necessary to calculate process-wide costs:
Cost |
Variable |
Name |
Description |
---|---|---|---|
Total capital cost |
\(C_{ca,tot}\) |
|
Total capital cost |
Unit capital cost |
\(C_{ca,u}\) |
|
Unit processes capital cost |
Total operating cost |
\(C_{op,tot}\) |
|
Total operating cost for unit process |
Total fixed operating cost |
\(C_{op,fix}\) |
|
Total fixed operating cost for unit process |
Total variable operating cost |
\(C_{op,var}\) |
|
Total variable operating cost for unit process |
Total annualized cost |
\(C_{annual}\) |
|
Total cost on a annualized basis |
Aggregate electricity cost |
\(C_{el,tot}\) |
|
Sum of all electricity costs |
Costing Calculations
Total annulized cost is a simple function of the annualized capital cost and the annualized operating cost:
\[C_{annual} = f_{crf} C_{ca,tot} + C_{op,tot}\]
The total capital cost is a simple factor of the sum of the unit model capital costs:
\[C_{ca,tot} = f_{toti} C_{ca,u}\]
The total operating cost is the sum of the fixed and variable operating costs:
\[C_{op,tot} = C_{op,fix} + c_{op,var}\]
The total fixed operating cost \(C_{op,fix}\) is the sum of the maintence, labor, and chemical operating costs, \(C_{mlc}\) and the total fixed operating costs from the unit models, \(C_{fop,u}\):
\[C_{op,fix} = C_{mlc} + C_{fop,u}\]
Where the maintenance-labor-chemical operating cost \(C_{mlc}\) is defined as:
\[C_{mlc} = f_{mlc} C_{ca,tot}\]
The total variable operating cost is the sum of the total variable operating cost from the unit models, \(C_{vop,u}\) plus the sum of the flow costs, \(C_{flow,tot}\) times the plant utilization factor \(f_{util}\):
\[C_{op,var} = C_{vop,u} + f_{util} C_{flow,tot}\]
Aggregates Metrics
Levelized Cost of Water (LCOW)
For a given volumetric flow \(Q\), the LCOW, \(LCOW_{Q}\) is calculated by the add_LCOW method as
\[LCOW_{Q} = \frac{f_{crf} C_{ca,tot} + C_{op,tot}}{f_{util} Q}\]
Specific Energy Consumption
For a given volumetric flow Q, the specific energy consumption, \(E_{spec,Q}\) is calculated by the add_specific_energy_consumption method as
\[E_{spec,Q} = \frac{C_{el,tot}}{Q}\]
Specific Electrical Carbon Intensity
For a given volumetric flow Q, the specific electrical carbon intensity, \(E^{C}_{spec,Q}\) is calculated by the add_specific_electrical_carbon_intensity method as
\[E^{C}_{spec,Q} = \frac{f_{eci} C_{el,tot}}{Q}\]
Annual Water Production
For a given volumetric flow Q, the annual water production, \(W^{A}_{Q}\) is calculated by the add_annual_water_production method as
\[W^{A}_{Q} = f_{util} Q\]
Default Costing Methods
While the expectation is that unit models use the self-registration process noted above, for interoperability with IDAES unit models the WaterTAPCostingBlockData class defines default costing methods for IDAES unit models:
Class Documentation
WaterTAPCostingBlockData