Source code for watertap.costing.costing_base

#################################################################################
# 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 idaes.core import declare_process_block_class
from idaes.core.base.costing_base import FlowsheetCostingBlockData
from idaes.models.unit_models import Mixer, HeatExchanger

from watertap.core.util.misc import is_constant_up_to_units

from watertap.costing.unit_models.mixer import cost_mixer
from watertap.costing.unit_models.heat_exchanger import cost_heat_exchanger


[docs]@declare_process_block_class("WaterTAPCostingBlock") class WaterTAPCostingBlockData(FlowsheetCostingBlockData): """ Base class for creating WaterTAP costing packages. Allows unit models to "self-register" their default costing methods, and for anonymous expressions in flow costs. """ # Define default mapping of costing methods to unit models unit_mapping = { Mixer: cost_mixer, HeatExchanger: cost_heat_exchanger, }
[docs] def build(self): super().build() self._registered_LCOWs = {}
[docs] def add_LCOW(self, flow_rate, name="LCOW"): """ Add Levelized Cost of Water (LCOW) to costing block. Args: flow_rate - flow rate of water (volumetric) to be used in calculating LCOW name (optional) - name for the LCOW variable (default: LCOW) """ LCOW = pyo.Var( doc=f"Levelized Cost of Water based on flow {flow_rate.name}", units=self.base_currency / pyo.units.m**3, ) self.add_component(name, LCOW) LCOW_constraint = pyo.Constraint( expr=LCOW == ( self.total_capital_cost * self.capital_recovery_factor + self.total_operating_cost ) / ( pyo.units.convert( flow_rate, to_units=pyo.units.m**3 / self.base_period ) * self.utilization_factor ), doc=f"Constraint for Levelized Cost of Water based on flow {flow_rate.name}", ) self.add_component(name + "_constraint", LCOW_constraint) self._registered_LCOWs[name] = (LCOW, LCOW_constraint)
[docs] def add_specific_energy_consumption( self, flow_rate, name="specific_energy_consumption" ): """ Add specific energy consumption (kWh/m**3) to costing block. Args: flow_rate - flow rate of water (volumetric) to be used in calculating specific energy consumption name (optional) - the name of the Expression for the specific energy consumption (default: specific_energy_consumption) """ self.add_component( name, pyo.Expression( expr=self.aggregate_flow_electricity / pyo.units.convert( flow_rate, to_units=pyo.units.m**3 / pyo.units.hr ), doc=f"Specific energy consumption based on flow {flow_rate.name}", ), )
[docs] def add_annual_water_production(self, flow_rate, name="annual_water_production"): """ Add annual water production to costing block. Args: flow_rate - flow rate of water (volumetric) to be used in calculating annual water production name (optional) - name for the annual water productionvariable Expression (default: annual_water_production) """ self.add_component( name, pyo.Expression( expr=( pyo.units.convert( flow_rate, to_units=pyo.units.m**3 / self.base_period ) * self.utilization_factor ), doc=f"Annual water production based on flow {flow_rate.name}", ), )
[docs] def add_electricity_intensity(self, flow_rate, name="electricity_intensity"): """ Add calculation of overall electricity intensity to costing block. Args: flow_rate - flow rate of water (volumetric) to be used in calculating electricity intensity name (optional) - the name of the Expression for the specific electrical intensity (default: specific_electrical_carbon_intensity) """ self.add_specific_energy_consumption(flow_rate, name=name)
[docs] def add_specific_electrical_carbon_intensity( self, flow_rate, name="specific_electrical_carbon_intensity" ): """ Add specific electrical carbon intensity (kg_CO2eq/m**3) to costing block. Args: flow_rate - flow rate of water (volumetric) to be used in calculating specific electrical carbon intensity name (optional) - the name of the Expression for the specific carbon intensity (default: specific_electrical_carbon_intensity) """ self.add_component( name, pyo.Expression( expr=self.aggregate_flow_electricity * self.electrical_carbon_intensity / pyo.units.convert( flow_rate, to_units=pyo.units.m**3 / pyo.units.hr ), doc=f"Specific electrical carbon intensity based on flow {flow_rate.name}", ), )
def _build_common_process_costs(self): """ Build the common process costs to WaterTAP Costing Packages. The currency units should already be registered. The derived class should add constraints for total_capital_cost and total_operating_cost """ self.total_capital_cost = pyo.Var( initialize=0, doc="Total capital cost of the process", units=self.base_currency, ) self.total_operating_cost = pyo.Var( initialize=0, doc="Total operating cost of process per operating period", units=self.base_currency / self.base_period, ) def _build_common_global_params(self): """ Build the global parameters common to WaterTAP Costing Packages. The currency units should already be registered. The derived class should define the capital_recovery_factor. """ self.utilization_factor = pyo.Var( initialize=0.9, doc="Plant capacity utilization [fraction of uptime]", units=pyo.units.dimensionless, ) self.electricity_cost = pyo.Var( initialize=0.07, doc="Electricity cost", units=pyo.units.USD_2018 / pyo.units.kWh, ) self.defined_flows["electricity"] = self.electricity_cost self.electrical_carbon_intensity = pyo.Var( initialize=0.475, doc="Grid carbon intensity [kgCO2_eq/kWh]", units=pyo.units.kg / pyo.units.kWh, ) self.capital_recovery_factor = pyo.Expression( expr=0, doc="Capital annualization factor [fraction of investment cost/year]", ) self.TPEC = pyo.Var( initialize=3.4, doc="Total Purchased Equipment Cost (TPEC)", units=pyo.units.dimensionless, ) self.TIC = pyo.Var( initialize=1.65, doc="Total Installed Cost (TIC)", units=pyo.units.dimensionless, ) self.fix_all_vars() def _get_costing_method_for(self, unit_model): """ Allow the unit model to register its default costing method, either through an attribute named "default_costing_method" or by naming the default costing method "default_costing_method" """ if hasattr(unit_model, "default_costing_method"): return unit_model.default_costing_method return super()._get_costing_method_for(unit_model)
[docs] def register_flow_type(self, flow_type, cost): """ This method allows users to register new material and utility flows with the FlowsheetCostingBlock for use when costing flows. If `cost` is a constant (up to units), then this method creates a new `Var` on the FlowsheetCostingBlock named f`{flow_type}_cost`. Otherwise `cost` is a non-constant expression and this method will create a new `Expression` on the FlowsheetCostingBlock named f`{flow_type}_cost` whose value is fixed to `cost`. If a component named f`{flow_type}_cost` already exists on the FlowsheetCostingBlock, then an error is raised unless f`{flow_type}_cost` is `cost`. If f`{flow_type}_cost` is `cost`, no error is raised and the existing component f`{flow_type}_cost` is used to cost the flow. Args: flow_type: string name to represent flow type cost: a Pyomo expression with units representing the flow cost """ flow_cost_name = flow_type + "_cost" current_flow_cost = self.component(flow_cost_name) if (current_flow_cost is None) and (not is_constant_up_to_units(cost)): cost_expr = pyo.Expression(expr=cost) self.add_component(flow_cost_name, cost_expr) super().register_flow_type(flow_type, cost_expr) else: # all other cases are handled in the base class super().register_flow_type(flow_type, cost)