Source code for watertap.core.zero_order_costing

#################################################################################
# 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/"
#################################################################################
"""
General costing package for zero-order processes.
"""
import os
import yaml

import pyomo.environ as pyo
from pyomo.common.config import ConfigValue
from pyomo.util.calc_var_value import calculate_variable_from_constraint

from idaes.core import declare_process_block_class
from idaes.core.base.costing_base import (
    FlowsheetCostingBlockData,
    register_idaes_currency_units,
)

global_params = [
    "plant_lifetime",
    "utilization_factor",
    "land_cost_percent_FCI",
    "working_capital_percent_FCI",
    "salaries_percent_FCI",
    "benefit_percent_of_salary",
    "maintenance_costs_percent_FCI",
    "laboratory_fees_percent_FCI",
    "insurance_and_taxes_percent_FCI",
    "wacc",
    "TPEC",
    "TIC",
]


[docs]@declare_process_block_class("ZeroOrderCosting") class ZeroOrderCostingData(FlowsheetCostingBlockData): """ General costing package for zero-order processes. """ CONFIG = FlowsheetCostingBlockData.CONFIG() CONFIG.declare( "case_study_definition", ConfigValue( default=None, doc="Path to YAML file defining global parameters for case study. If " "not provided, default values from the WaterTap database are used.", ), )
[docs] def build_global_params(self): """ To minimize overhead, only create global parameters for now. Unit-specific parameters will be added as sub-Blocks on a case-by-case basis as a unit of that type is costed. """ # Load case study definition from file cs_def = _load_case_study_definition(self) # Register currency and conversion rates if "currency_definitions" in cs_def: pyo.units.load_definitions_from_strings(cs_def["currency_definitions"]) else: register_idaes_currency_units() # Set the base year for all costs self.base_currency = getattr(pyo.units, cs_def["base_currency"]) # Set a base period for all operating costs self.base_period = getattr(pyo.units, cs_def["base_period"]) # Define expected flows self.defined_flows = {} for f, v in cs_def["defined_flows"].items(): self.defined_flows[f] = v["value"] * getattr(pyo.units, v["units"]) # Costing factors self.plant_lifetime = pyo.Var(units=self.base_period, doc="Plant lifetime") self.utilization_factor = pyo.Var( units=pyo.units.dimensionless, doc="Plant capacity utilization [%]" ) self.land_cost_percent_FCI = pyo.Var( units=pyo.units.dimensionless, doc="Land cost as % FCI" ) self.working_capital_percent_FCI = pyo.Var( units=pyo.units.dimensionless, doc="Working capital as % FCI" ) self.salaries_percent_FCI = pyo.Var( units=1 / self.base_period, doc="Salaries as % FCI" ) self.benefit_percent_of_salary = pyo.Var( units=pyo.units.dimensionless, doc="Benefits as % salaries" ) self.maintenance_costs_percent_FCI = pyo.Var( units=1 / self.base_period, doc="Maintenance and contingency costs as % FCI" ) self.laboratory_fees_percent_FCI = pyo.Var( units=1 / self.base_period, doc="Laboratory fees as % FCI" ) self.insurance_and_taxes_percent_FCI = pyo.Var( units=1 / self.base_period, doc="Insurance and taxes as % FCI" ) self.wacc = pyo.Var( units=pyo.units.dimensionless, doc="Weighted Average Cost of Capital (WACC)" ) self.capital_recovery_factor = pyo.Expression( expr=( ( self.wacc * (1 + self.wacc) ** (self.plant_lifetime / self.base_period) ) / (((1 + self.wacc) ** (self.plant_lifetime / self.base_period)) - 1) / self.base_period ) ) self.TPEC = pyo.Var( units=pyo.units.dimensionless, doc="Total Purchased Equipment Cost (TPEC)" ) self.TIC = pyo.Var( units=pyo.units.dimensionless, doc="Total Installed Cost (TIC)" ) # Fix all Vars from database for v in global_params: try: value = cs_def["global_parameters"][v]["value"] units = cs_def["global_parameters"][v]["units"] getattr(self, v).fix(value * getattr(pyo.units, units)) except KeyError: raise KeyError( f"Invalid case study definition file - no entry found " f"for {v}, or entry lacks value and units." )
[docs] def build_process_costs(self): """ Calculating process wide costs. """ # Other capital costs self.land_cost = pyo.Var( initialize=0, units=self.base_currency, doc="Land costs - based on aggregate capital costs", ) self.working_capital = pyo.Var( initialize=0, units=self.base_currency, doc="Working capital - based on aggregate capital costs", ) self.total_capital_cost = pyo.Var( initialize=0, units=self.base_currency, doc="Total capital cost of process" ) self.land_cost_constraint = pyo.Constraint( expr=self.land_cost == self.aggregate_capital_cost * self.land_cost_percent_FCI ) self.working_capital_constraint = pyo.Constraint( expr=self.working_capital == self.aggregate_capital_cost * self.working_capital_percent_FCI ) self.total_capital_cost_constraint = pyo.Constraint( expr=self.total_capital_cost == self.aggregate_capital_cost + self.land_cost + self.working_capital ) # Other fixed costs self.salary_cost = pyo.Var( initialize=0, units=self.base_currency / self.base_period, doc="Salary costs - based on aggregate capital costs", ) self.benefits_cost = pyo.Var( initialize=0, units=self.base_currency / self.base_period, doc="Benefits costs - based on percentage of salary costs", ) self.maintenance_cost = pyo.Var( initialize=0, units=self.base_currency / self.base_period, doc="Maintenance costs - based on aggregate capital costs", ) self.laboratory_cost = pyo.Var( initialize=0, units=self.base_currency / self.base_period, doc="Laboratory costs - based on aggregate capital costs", ) self.insurance_and_taxes_cost = pyo.Var( initialize=0, units=self.base_currency / self.base_period, doc="Insurance and taxes costs - based on aggregate capital costs", ) self.total_fixed_operating_cost = pyo.Var( initialize=0, units=self.base_currency / self.base_period, doc="Total fixed operating costs", ) self.salary_cost_constraint = pyo.Constraint( expr=self.salary_cost == self.aggregate_capital_cost * self.salaries_percent_FCI ) self.benefits_cost_constraint = pyo.Constraint( expr=self.benefits_cost == self.salary_cost * self.benefit_percent_of_salary ) self.maintenance_cost_constraint = pyo.Constraint( expr=self.maintenance_cost == self.aggregate_capital_cost * self.maintenance_costs_percent_FCI ) self.laboratory_cost_constraint = pyo.Constraint( expr=self.laboratory_cost == self.aggregate_capital_cost * self.laboratory_fees_percent_FCI ) self.insurance_and_taxes_cost_constraint = pyo.Constraint( expr=self.insurance_and_taxes_cost == self.aggregate_capital_cost * self.insurance_and_taxes_percent_FCI ) self.total_fixed_operating_cost_constraint = pyo.Constraint( expr=self.total_fixed_operating_cost == self.aggregate_fixed_operating_cost + self.salary_cost + self.benefits_cost + self.maintenance_cost + self.laboratory_cost + self.insurance_and_taxes_cost ) # Other variable costs self.total_variable_operating_cost = pyo.Expression( expr=self.aggregate_variable_operating_cost + sum(self.aggregate_flow_costs[f] for f in self.used_flows) * self.utilization_factor, doc="Total variable operating cost of process per operating period", ) self.total_operating_cost = pyo.Expression( expr=(self.total_fixed_operating_cost + self.total_variable_operating_cost), doc="Total operating cost of process per operating period", )
[docs] def initialize_build(self): """ Basic initialization for flowsheet level quantities """ calculate_variable_from_constraint(self.land_cost, self.land_cost_constraint) calculate_variable_from_constraint( self.working_capital, self.working_capital_constraint ) calculate_variable_from_constraint( self.total_capital_cost, self.total_capital_cost_constraint ) calculate_variable_from_constraint( self.salary_cost, self.salary_cost_constraint ) calculate_variable_from_constraint( self.benefits_cost, self.benefits_cost_constraint ) calculate_variable_from_constraint( self.maintenance_cost, self.maintenance_cost_constraint ) calculate_variable_from_constraint( self.laboratory_cost, self.laboratory_cost_constraint ) calculate_variable_from_constraint( self.insurance_and_taxes_cost, self.insurance_and_taxes_cost_constraint ) calculate_variable_from_constraint( self.total_fixed_operating_cost, self.total_fixed_operating_cost_constraint )
[docs] def add_LCOW(self, flow_rate): """ Add Levelized Cost of Water (LCOW) to costing block. Args: flow_rate - flow rate of water (volumetric) to be used in calculating LCOW """ self.LCOW = pyo.Expression( expr=( 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="Levelized Cost of Water", )
[docs] def add_electricity_intensity(self, flow_rate): """ Add calculation of overall electricity intensity to costing block. Args: flow_rate - flow rate of water (volumetric) to be used in calculating electricity intensity """ self.electricity_intensity = pyo.Expression( expr=pyo.units.convert( self.aggregate_flow_electricity / flow_rate, to_units=pyo.units.kWh / pyo.units.m**3, ), doc="Overall electricity intensity", )
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)
def _load_case_study_definition(self): """ Load data from case study definition file into a Python dict. If users did not provide a definition file as a config argument, the default definition from the WaterTap techno-economic database is used. """ source_file = self.config.case_study_definition if source_file is None: source_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), "..", "data", "techno_economic", "default_case_study.yaml", ) try: with open(source_file, "r") as f: lines = f.read() f.close() except OSError: raise OSError( "Could not find specified case study definition file. " "Please check the path provided." ) return yaml.load(lines, yaml.Loader)