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:

  1. 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,
)
  1. 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)
  1. 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
  1. 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}\)

utilization_factor

90%

Percentage of year plant is operating

Electricity price

\(P\)

electricity_cost

$0.07/kWh

Electricity price in 2018 USD

Electricity carbon intensity

\(f_{eci}\)

electrical_carbon_intensity

0.475 kg/kWh

Carbon intensity of electricity

Capital recovery factor

\(f_{crf}\)

capital_recovery_factor

10%

Capital annualization (fraction of investment cost/year)

Plant lifetime

\(L\)

plant_lifetime

30 years

Plant lifetime

Weighted average cost of capital

\(f_{wacc}\)

wacc

9.30734%

Average cost of capital over plant lifetime

Total purchased equipment cost (TPEC)

\(f_{TPEC}\)

TPEC

4.121212

Common indirect capital cost multiplier for unit models

Total installed cost (TIC)

\(f_{TIC}\)

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}\)

total_investment_factor

None

Total investment factor (investment cost / equipment cost)

Maintenance-labor-chemical factor

\(f_{mlc}\)

maintenance_labor_chemical_factor

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

Total capital cost

Unit capital cost

\(C_{ca,u}\)

aggregate_capital_cost

Unit processes capital cost

Total operating cost

\(C_{op,tot}\)

total_operating_cost

Total operating cost for unit process

Total fixed operating cost

\(C_{op,fix}\)

total_fixed_operating_cost

Total fixed operating cost for unit process

Total variable operating cost

\(C_{op,var}\)

total_variable_operating_cost

Total variable operating cost for unit process

Total annualized cost

\(C_{annual}\)

total_annualized_costs

Total cost on a annualized basis

Aggregate electricity cost

\(C_{el,tot}\)

aggregate_electricity_cost

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