Source code for idaes.models.costing.SSLW

#################################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2024 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved.  Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
"""
Costing package based on methods from:

    Process and Product Design Principles: Synthesis, Analysis, and
    Evaluation
    Seider, Seader, Lewin, Windagdo, 3rd Ed. John Wiley and Sons
    Chapter 22. Cost Accounting and Capital Cost Estimation
    22.2 Cost Indexes and Capital Investment

Currently, this costing package only includes methods for capital costing of
unit operations.
"""
# TODO: Missing docstrings
# pylint: disable=missing-class-docstring

import pyomo.environ as pyo

# TODO: HX1D not supported - does not define area (has shell_area & tube_area)
from idaes.models.unit_models import (
    Compressor,
    CSTR,
    Flash,
    Heater,
    HeatExchanger,
    HeatExchangerNTU,
    PFR,
    PressureChanger,
    Pump,
    StoichiometricReactor,
    Turbine,
)
from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption
from idaes.models.unit_models.heat_exchanger import HeatExchangerFlowPattern

from idaes.core import declare_process_block_class
from idaes.core.util.exceptions import ConfigurationError
from idaes.core.util.constants import Constants
from idaes.core.util.math import smooth_max
from idaes.core.util.misc import StrEnum

from idaes.core import (
    FlowsheetCostingBlockData,
    register_idaes_currency_units,
)

import idaes.logger as idaeslog

_log = idaeslog.getLogger(__name__)

# Some more information about this module
__author__ = "Miguel Zamarripa, Andrew Lee"


class HXType(StrEnum):
    floating_head = "floating_head"
    fixed_head = "fixed_head"
    Utube = "Utube"
    kettle_vap = "Kettle_vap"


class HXMaterial(StrEnum):
    CarbonSteelCarbonSteel = "CarbonSteelCarbonSteel"
    CarbonSteelBrass = "CarbonSteelBrass"
    CarbonSteelStainlessSteel = "CarbonSteelStainlessSteel"
    CarbonSteelMonel = "CarbonSteelMonel"
    CarbonSteelTitanium = "CarbonSteelTitanium"
    CarbonSteelCrMoSteel = "CarbonSteelCrMoSteel"
    CrMoSteelCrMoSteel = "CrMoSteelCrMoSteel"
    StainlessSteelStainlessSteel = "StainlessSteelStainlessSteel"
    MonelMonel = "MonelMonel"
    TitaniumTitanium = "TitaniumTitanium"


class HXTubeLength(StrEnum):
    EightFoot = "8ft"
    TwelveFoot = "12ft"
    SixteenFoot = "16ft"
    TwentyFoot = "20ft"


class VesselMaterial(StrEnum):
    CarbonSteel = "Carbon_steel"
    LowAlloySteel = "LowAlloySteel"
    StainlessSteel304 = "StainlessSteel304"
    StainlessSteel316 = "StainlessSteel316"
    Carpenter20CB3 = "Carpenter20CB3"
    Nickel200 = "Nickel200"
    Monel400 = "Monel400"
    Inconel600 = "Inconel600"
    Incoloy825 = "Incoloy825"
    Titanium = "Titanium"


class TrayType(StrEnum):
    Sieve = "Sieve"
    Valve = "Valve"
    BubbleCap = "BubbleCap"


class TrayMaterial(StrEnum):
    CarbonSteel = "CarbonSteel"
    StainlessSteel303 = "StainlessSteel303"
    StainlessSteel316 = "StainlessSteel316"
    Carpenter20CB3 = "Carpenter20CB3"
    Monel = "Monel"


class HeaterMaterial(StrEnum):
    CarbonSteel = "CarbonSteel"
    CrMoSteel = "CrMoSteel"
    StainlessSteel = "StainlessSteel"


class HeaterSource(StrEnum):
    Fuel = "Fuel"
    Reformer = "Reformer"
    Pyrolysis = "Pyrolysis"
    HotWater = "HotWater"
    Salts = "Salts"
    DowthermA = "DowthermA"
    steamBoiler = "SteamBoiler"


class CompressorType(StrEnum):
    Centrifugal = "Centrifugal"
    Reciprocating = "Reciprocating"
    Screw = "Screw"


class CompressorDriveType(StrEnum):
    ElectricMotor = "ElectricMotor"
    SteamTurbine = "SteamTurbine"
    gasTurbine = "GasTurbine"


class CompressorMaterial(StrEnum):
    CarbonSteel = "CarbonSteel"
    StainlessSteel = "StainlessSteel"
    NickelAlloy = "NickelAlloy"


class PumpMaterial(StrEnum):
    CastIron = "CastIron"
    DuctileIron = "DuctileIron"
    CastSteel = "CastSteel"
    Bronze = "Bronze"
    StainlessSteel = "StainlessSteel"
    HastelloyC = "HastelloyC"
    Monel = "Monel"
    Nickel = "Nickel"
    Titanium = "Titanium"
    NiAlBronze = "NiAlBronze"
    CarbonSteel = "CarbonSteel"


class PumpType(StrEnum):
    Centrifugal = "Centrifugal"
    ExternalGear = "ExternalGear"
    Reciprocating = "Reciprocating"


class PumpMotorType(StrEnum):
    Open = "open"
    Enclosed = "enclosed"
    ExplosionProof = "explosion_proof"


class FanType(StrEnum):
    CentrifugalBackward = "CentrifugalBackward"
    CentrifugalStraight = "CentrifugalStraight"
    VaneAxial = "VaneAxial"
    TubeAxial = "TubeAxial"


class FanMaterial(StrEnum):
    CarbonSteel = "CarbonSteel"
    Fiberglass = "Fiberglass"
    StainlessSteel = "StainlessSteel"
    NickelAlloy = "NickelAlloy"


class BlowerType(StrEnum):
    Centrifugal = "Centrifugal"
    Rotary = "Rotary"


class BlowerMaterial(StrEnum):
    CarbonSteel = "CarbonSteel"
    Aluminum = "Aluminum"
    Fiberglass = "Fiberglass"
    StainlessSteel = "StainlessSteel"
    NickelAlloy = "NickelAlloy"


[docs] @declare_process_block_class("SSLWCosting") class SSLWCostingData(FlowsheetCostingBlockData): # Register currency and conversion rates based on CE Index register_idaes_currency_units()
[docs] def build_global_params(self): """ This is where we can declare any global parameters we need, such as Lang factors, or coefficients for costing methods that should be shared across the process. You can do what you want here, so you could have e.g. sub-Blocks for each costing method to separate the parameters for each method. """ # Set the base year for all costs self.base_currency = pyo.units.USD_2018 # Set a base period for all operating costs self.base_period = pyo.units.year
[docs] def build_process_costs(self): """ This is where you do all your process wide costing. This is completely up to you, but you will have access to the following aggregate costs: 1. self.aggregate_capital_cost 2. self.aggregate_fixed_operating_cost 3. self.aggregate_variable_operating_cost 4. self.aggregate_flow_costs (indexed by flow type) """
# TODO: Do we have any process level methods to add here?
[docs] @staticmethod def initialize_build(*args, **kwargs): """ Here we can add initialization steps for the things we built in build_process_costs. Note that the aggregate costs will be initialized by the framework. """
# TODO: For now, no additional process level costs to initialize
[docs] def cost_heat_exchanger( blk, hx_type=HXType.Utube, material_type=HXMaterial.StainlessSteelStainlessSteel, tube_length=HXTubeLength.TwelveFoot, integer=True, ): """ Heat exchanger costing method. This method computes the purchase cost (CP) for a shell and tube heat exchanger (Eq. 22.43), the model computes the base cost (CB for 4 types of heat exchangers, such as floating head, fixed head, U-tube, and Kettle vaporizer), construction material factor (mat_factor), pressure design factor (pressure_factor), and tube length correction factor (length_factor), using Chemical Engineering base cost index of 500. Purchase Cost = pressure_factor * material_factor * length_factor * base_cost_per_unit * number_of_units Args: hx_type: HXType Enum indicating type of heat exchanger design, default = HXType.Utube. material_type: HXMaterial Enum indicating material of construction, default = HXMaterial.StainlessSteelStainlessSteel. tube_length: HXTubeLength Enum indicating length of HX tubes, default = HXTubeLength.TwelveFoot. integer: whether the number of units should be constrained to be an integer or not (default = True). """ # Validate arguments if hx_type not in HXType: raise ConfigurationError( f"{blk.unit_model.name} received invalid argument for hx_type:" f" {hx_type}. Argument must be a member of the HXType Enum." ) if material_type not in HXMaterial: raise ConfigurationError( f"{blk.unit_model.name} received invalid argument for " f"material_type: {material_type}. Argument must be a member " "of the HXMaterial Enum." ) if tube_length not in HXTubeLength: raise ConfigurationError( f"{blk.unit_model.name} received invalid argument for " f"tube_length: {tube_length}. Argument must be a member " "of the HXTubeLength Enum." ) # Build generic costing variables _make_common_vars(blk, integer) # Length correction factor c_fl = {"8ft": 1.25, "12ft": 1.12, "16ft": 1.05, "20ft": 1.00} blk.length_factor = pyo.Param( mutable=True, initialize=c_fl[tube_length], doc="HX tube length correction factor", ) blk.hx_oversize = pyo.Param( mutable=True, initialize=1.1, doc="HX oversize factor (1.1 to 1.5)", units=pyo.units.ft**-2, ) # -------------------------------------------------- # Base cost calculation - based on selected heat exchanger type: alpha_dict = { HXType.floating_head: {1: 11.9052, 2: 0.8709, 3: 0.09005}, HXType.fixed_head: {1: 11.2927, 2: 0.8228, 3: 0.09861}, HXType.Utube: {1: 11.3852, 2: 0.9186, 3: 0.09790}, HXType.kettle_vap: {1: 12.2052, 2: 0.8709, 3: 0.09005}, } alpha = alpha_dict[hx_type] # Convert area to square feet area_unit = ( pyo.units.convert(blk.unit_model.area, to_units=pyo.units.ft**2) / blk.number_of_units ) @blk.Constraint() def base_cost_per_unit_eq(blk): return ( blk.base_cost_per_unit == pyo.exp( alpha[1] - alpha[2] * pyo.log(area_unit * blk.hx_oversize) + alpha[3] * pyo.log(area_unit * blk.hx_oversize) ** 2 ) * pyo.units.USD_CE500 ) @blk.Expression(doc="Base cost for all installed units") def base_cost(blk): return blk.base_cost_per_unit * blk.number_of_units # ------------------------------------------------------ # Material of construction factor Eq. 22.44 in the reference blk.material_factor = pyo.Var( initialize=3.5, domain=pyo.NonNegativeReals, doc="Construction material correction factor", ) hx_material_factor_dict = { HXMaterial.CarbonSteelCarbonSteel: {"A": 0.00, "B": 0.00}, HXMaterial.CarbonSteelBrass: {"A": 1.08, "B": 0.05}, HXMaterial.CarbonSteelStainlessSteel: {"A": 1.75, "B": 0.13}, HXMaterial.CarbonSteelMonel: {"A": 2.10, "B": 0.13}, HXMaterial.CarbonSteelTitanium: {"A": 5.20, "B": 0.16}, HXMaterial.CarbonSteelCrMoSteel: {"A": 1.55, "B": 0.05}, HXMaterial.CrMoSteelCrMoSteel: {"A": 1.70, "B": 0.07}, HXMaterial.StainlessSteelStainlessSteel: {"A": 2.70, "B": 0.07}, HXMaterial.MonelMonel: {"A": 3.30, "B": 0.08}, HXMaterial.TitaniumTitanium: {"A": 9.60, "B": 0.06}, } mf = hx_material_factor_dict[material_type] @blk.Constraint() def hx_material_eqn(self): if material_type == HXMaterial.CarbonSteelCarbonSteel: return blk.material_factor == 1 else: return ( blk.material_factor == mf["A"] + (area_unit / (100 * pyo.units.ft**2)) ** mf["B"] ) # ------------------------------------------------------ # Pressure factor calculation t0 = blk.unit_model.flowsheet().time.first() # Assume higher pressure fluid is tube side blk.pressure_factor = pyo.Var( initialize=1, domain=pyo.NonNegativeReals, doc="Pressure design factor" ) try: tube_props = blk.unit_model.hot_side.properties_in[t0] except AttributeError: # Assume HX1D if blk.unit_model.config.flow_type == HeatExchangerFlowPattern.cocurrent: inlet_x = 0 else: inlet_x = 1 tube_props = blk.unit_model.tube.properties[0, inlet_x] # Pressure units must be in psig pressure = pyo.units.convert( tube_props.pressure, to_units=pyo.units.psi ) - pyo.units.convert(1 * pyo.units.atm, to_units=pyo.units.psi) @blk.Constraint() def p_factor_eq(blk): # Equation valid from 600 pisg to 3000 psig # return self.pressure_factor == ( # 0.8510 + 0.1292*(pressure/600) + 0.0198*(pressure/600)**2) # Equation valid from 100 pisg to 2000 psig return blk.pressure_factor == ( 0.9803 + 0.0180 * (pressure / (100 * pyo.units.psi)) + 0.0017 * (pressure / (100 * pyo.units.psi)) ** 2 ) # Total capital cost equation @blk.Constraint() def capital_cost_constraint(blk): return blk.capital_cost == ( blk.pressure_factor * blk.material_factor * blk.length_factor * blk.base_cost )
[docs] def cost_vessel( blk, vertical=False, material_type=VesselMaterial.CarbonSteel, shell_thickness=1.25 * pyo.units.inch, weight_limit=1, aspect_ratio_range=1, include_platforms_ladders=True, vessel_diameter=None, vessel_length=None, number_of_units=1, number_of_trays=None, tray_material=TrayMaterial.CarbonSteel, tray_type=TrayType.Sieve, ): """ Generic vessel costing method. Args: vertical: alignment of vessel; vertical if True, horizontal if False (default=False). material_type: VesselMaterial Enum indicating material of construction, default = VesselMaterial.CarbonSteel. shell_thickness: thickness of vessel shell, including pressure allowance. Default = 1.25 inches. weight_limit: 1: (default) 1000 to 920,000 lb, 2: 4200 to 1M lb. Option 2 is only valid for vertical vessels. aspect_ratio_range: vertical vessels only, default = 1; 1: 3 < D < 21 ft, 12 < L < 40 ft, 2: 3 < D < 24 ft; 27 < L < 170 ft. include_platforms_ladders: whether to include platforms and ladders in costing , default = True. vessel_diameter: Pyomo component representing vessel diameter. If not provided, assumed to be named "diameter" vessel_length: Pyomo component representing vessel length. If not provided, assumed to be named "length". number_of_units: Integer or Pyomo component representing the number of parallel units to be costed, default = 1. number_of_trays: Pyomo component representing the number of distillation trays in vessel (default=None) tray_material: Only required if number_of_trays is not None. TrayMaterial Enum indicating material of construction for distillation trays, default = TrayMaterial.CarbonSteel. tray_type: Only required if number_of_trays is not None. TrayType Enum indicating type of distillation trays to use, default = TrayMaterial.Sieve. """ # Build generic costing variables blk.base_cost_per_unit = pyo.Var( initialize=1e5, domain=pyo.NonNegativeReals, units=pyo.units.USD_CE500, doc="Base cost per unit", ) blk.capital_cost = pyo.Var( initialize=1e4, domain=pyo.NonNegativeReals, bounds=(0, None), units=pyo.units.USD_CE500, doc="Capital cost of all units", ) # Check arguments if material_type not in VesselMaterial: raise ConfigurationError( f"{blk.unit_model.name} received invalid argument for " f"material_type: {material_type}. Argument must be a member " "of the VesselMaterial Enum." ) if not vertical and number_of_trays is not None: raise ConfigurationError( f"{blk.unit_model.name} distillation trays are only supported " "for vertical vessels." ) if weight_limit == 2 and not vertical: raise ConfigurationError( f"{blk.unit_model.name} weight_limit option 2 is only valid " "for vertical vessels." ) elif weight_limit not in [1, 2]: raise ConfigurationError( f"{blk.unit_model.name} weight_limit argument must be 1 or 2;" f" received {weight_limit}." ) # Check references to diameter and length if vessel_diameter is None: try: vessel_diameter = blk.unit_model.diameter except AttributeError: raise ConfigurationError( f"{blk.unit_model.name} does not have a component named " "diameter. Please provide a reference to the vessel " "diameter as an argument to cost_unit." ) if vessel_length is None: try: vessel_length = blk.unit_model.length except AttributeError: raise ConfigurationError( f"{blk.unit_model.name} does not have a component named " "length. Please provide a reference to the vessel " "length as an argument to cost_unit." ) D_in = pyo.units.convert(vessel_diameter, to_units=pyo.units.inch) L_in = pyo.units.convert(vessel_length, to_units=pyo.units.inch) # Material densities in lb/cubic inch material_factor_dict = { VesselMaterial.CarbonSteel: {"factor": 1.0, "density": 0.284}, VesselMaterial.LowAlloySteel: {"factor": 1.2, "density": 0.271}, VesselMaterial.StainlessSteel304: {"factor": 1.7, "density": 0.270}, VesselMaterial.StainlessSteel316: {"factor": 2.1, "density": 0.276}, VesselMaterial.Carpenter20CB3: {"factor": 3.2, "density": 0.29}, VesselMaterial.Nickel200: {"factor": 5.4, "density": 0.3216}, VesselMaterial.Monel400: {"factor": 3.6, "density": 0.319}, VesselMaterial.Inconel600: {"factor": 3.9, "density": 0.3071}, VesselMaterial.Incoloy825: {"factor": 3.7, "density": 0.2903}, VesselMaterial.Titanium: {"factor": 7.7, "density": 0.1628}, } material_factors = material_factor_dict[material_type] # Users should calculate the pressure design based shell thickness # Pressure factor assumed to be included in thickness blk.shell_thickness = pyo.Param( mutable=True, doc="Shell thickness", units=pyo.units.inch ) blk.shell_thickness.set_value(shell_thickness) blk.material_factor = pyo.Param( initialize=material_factors["factor"], mutable=True, doc="Construction material correction factor", ) blk.material_density = pyo.Param( initialize=material_factors["density"], mutable=True, doc="Density of the metal", units=pyo.units.pound / pyo.units.inch**3, ) # Calculate weight of vessel blk.weight = pyo.Var( initialize=1000, domain=pyo.NonNegativeReals, doc="Weight of vessel in lb", units=pyo.units.pound, ) @blk.Constraint() def weight_eq(blk): return blk.weight == ( Constants.pi * (D_in + blk.shell_thickness) * (L_in + 0.8 * D_in) * blk.shell_thickness * blk.material_density ) # Base Vessel cost # Alpha factors for correlation # 2nd key is weight_limit option alpha_dict = { "H": {1: {1: 8.9552, 2: -0.2330, 3: 0.04333}, 2: None}, "V": { 1: {1: 7.0132, 2: 0.18255, 3: 0.02297}, 2: {1: 7.2756, 2: 0.18255, 3: 0.02297}, }, } if vertical: alpha = alpha_dict["V"][weight_limit] else: alpha = alpha_dict["H"][weight_limit] @blk.Constraint() def base_cost_constraint(blk): return blk.base_cost_per_unit == ( pyo.exp( alpha[1] + alpha[2] * (pyo.log(blk.weight / pyo.units.pound)) + alpha[3] * (pyo.log(blk.weight / pyo.units.pound) ** 2) ) * pyo.units.USD_CE500 ) # Add platform and ladder costs if required if include_platforms_ladders: SSLWCostingData._cost_platforms_ladders( blk, vertical=vertical, aspect_ratio_range=aspect_ratio_range, vessel_diameter=vessel_diameter, vessel_length=vessel_length, ) # Add distillation trays costs if required if number_of_trays is not None: SSLWCostingData._cost_distillation_trays( blk, tray_material=tray_material, tray_type=tray_type, vessel_diameter=vessel_diameter, number_of_trays=number_of_trays, ) # Total capital cost of vessel and ancillary equipment @blk.Constraint() def capital_cost_constraint(blk): cost_expr = blk.material_factor * blk.base_cost_per_unit if include_platforms_ladders: cost_expr += blk.base_cost_platforms_ladders if number_of_trays is not None: cost_expr += blk.base_cost_trays return blk.capital_cost == cost_expr * number_of_units
def _cost_platforms_ladders( blk, vertical, aspect_ratio_range, vessel_diameter, vessel_length ): """ Method for calculating costs of platforms and ladders """ blk.base_cost_platforms_ladders = pyo.Var( initialize=1000, units=pyo.units.USD_CE500, domain=pyo.NonNegativeReals, doc="Base cost of platforms and ladders", ) D = pyo.units.convert(vessel_diameter, to_units=pyo.units.foot) L = pyo.units.convert(vessel_length, to_units=pyo.units.foot) if not vertical: @blk.Constraint() def cost_platforms_ladders_eq(blk): return blk.base_cost_platforms_ladders == ( 2005 * (D / pyo.units.foot) ** 0.20294 * pyo.units.USD_CE500 ) else: @blk.Constraint() def cost_platforms_ladders_eq(blk): if aspect_ratio_range == 1: return blk.base_cost_platforms_ladders == ( 361.8 * (D / pyo.units.foot) ** 0.73960 * (L / pyo.units.foot) ** 0.70684 * pyo.units.USD_CE500 ) elif aspect_ratio_range == 2: return blk.base_cost_platforms_ladders == ( 309.9 * (D / pyo.units.foot) ** 0.63316 * (L / pyo.units.foot) ** 0.80161 * pyo.units.USD_CE500 ) else: raise ConfigurationError( f"{blk.unit_model.name} received invalid value for " f"aspect_ratio_range argument: {aspect_ratio_range}. " "Value must be 1 or 2." ) def _cost_distillation_trays( blk, tray_material, tray_type, vessel_diameter, number_of_trays ): # Check arguments if tray_material not in TrayMaterial: raise ConfigurationError( f"{blk.unit_model.name} received invalid argument for " f"tray_material: {tray_material}. Argument must be a member " "of the TrayMaterial Enum." ) if tray_type not in TrayType: raise ConfigurationError( f"{blk.unit_model.name} received invalid argument for " f"tray_type: {tray_type}. Argument must be a member " "of the TrayType Enum." ) D = pyo.units.convert(vessel_diameter, to_units=pyo.units.foot) blk.base_cost_trays = pyo.Var( initialize=1e6, units=pyo.units.USD_CE500, domain=pyo.NonNegativeReals, doc="Purchase cost of trays", ) tray_type_dict = { TrayType.Sieve: 1, TrayType.Valve: 1.18, TrayType.BubbleCap: 1.87, } blk.tray_type_factor = pyo.Param( initialize=tray_type_dict[tray_type], mutable=True, doc="Tray type factor" ) blk.tray_material_factor = pyo.Var( initialize=1.0, doc="FTM material of construction factor for trays" ) # Alpha parameters for material factor alpha = { TrayMaterial.CarbonSteel: {1: 1, 2: 0}, TrayMaterial.StainlessSteel303: {1: 1.189, 2: 0.0577}, TrayMaterial.StainlessSteel316: {1: 1.401, 2: 0.0724}, TrayMaterial.Carpenter20CB3: {1: 1.525, 2: 0.0788}, TrayMaterial.Monel: {1: 2.306, 2: 0.1120}, } # Calculating tray factor value # Column diameter in ft, eqn. valid for 2 to 16 ft @blk.Constraint() def tray_material_factor_eq(blk): return blk.tray_material_factor == ( alpha[tray_material][1] + alpha[tray_material][2] * D / pyo.units.foot ) # Calculate cost factor for number of trays blk.number_trays_factor = pyo.Var( initialize=1, units=pyo.units.dimensionless, doc="Cost factor for number of trays", ) @blk.Constraint() def num_tray_factor_constraint(blk): return blk.number_trays_factor == smooth_max( 1, 2.25 / (1.0414**number_of_trays) ) # Calculate base cost of a single tray blk.base_cost_per_tray = pyo.Var( initialize=1e4, domain=pyo.NonNegativeReals, units=pyo.units.USD_CE500, doc="Base cost of a single tray", ) @blk.Constraint() def single_tray_cost_constraint(blk): return blk.base_cost_per_tray == ( 468.00 * pyo.exp(0.1739 * D / pyo.units.foot) * pyo.units.USD_CE500 ) # Capital cost of trays @blk.Constraint() def tray_costing_constraint(blk): return blk.base_cost_trays == ( number_of_trays * blk.number_trays_factor * blk.tray_type_factor * blk.tray_material_factor * blk.base_cost_per_tray )
[docs] def cost_vertical_vessel( blk, material_type=VesselMaterial.CarbonSteel, shell_thickness=1.25 * pyo.units.inch, weight_limit=1, aspect_ratio_range=1, include_platforms_ladders=True, vessel_diameter=None, vessel_length=None, number_of_units=1, number_of_trays=None, tray_material=TrayMaterial.CarbonSteel, tray_type=TrayType.Sieve, ): """ Specific case of vessel costing method for vertical vessels. Args: material_type: VesselMaterial Enum indicating material of construction, default = VesselMaterial.CarbonSteel. shell_thickness: thickness of vessel shell, including pressure allowance. Default = 1.25 inches. weight_limit: 1: (default) 1000 to 920,000 lb, 2: 4200 to 1M lb. aspect_ratio_range: default = 1; 1: 3 < D < 21 ft, 12 < L < 40 ft, 2: 3 < D < 24 ft; 27 < L < 170 ft. include_platforms_ladders: whether to include platforms and ladders in costing , default = True. vessel_diameter: Pyomo component representing vessel diameter. If not provided, assumed to be named "diameter" vessel_length: Pyomo component representing vessel length. If not provided, assumed to be named "length". number_of_units: Integer or Pyomo component representing the number of parallel units to be costed, default = 1. number_of_trays: Pyomo component representing the number of distillation trays in vessel (default=None) tray_material: Only required if number_of_trays is not None. TrayMaterial Enum indicating material of construction for distillation trays, default = TrayMaterial.CarbonSteel. tray_type: Only required if number_of_trays is not None. TrayType Enum indicating type of distillation trays to use, default = TrayMaterial.Sieve. """ SSLWCostingData.cost_vessel( blk, vertical=True, material_type=VesselMaterial.CarbonSteel, shell_thickness=1.25 * pyo.units.inch, weight_limit=1, aspect_ratio_range=1, include_platforms_ladders=True, vessel_diameter=None, vessel_length=None, number_of_units=1, number_of_trays=None, tray_material=TrayMaterial.CarbonSteel, tray_type=TrayType.Sieve, )
[docs] def cost_horizontal_vessel( blk, material_type=VesselMaterial.CarbonSteel, shell_thickness=1.25 * pyo.units.inch, include_platforms_ladders=True, vessel_diameter=None, vessel_length=None, number_of_units=1, ): """ Specific case of vessel costing method for horizontal vessels. Arguments which do not apply to horizontal vessels are excluded. Args: material_type: VesselMaterial Enum indicating material of construction, default = VesselMaterial.CarbonSteel. shell_thickness: thickness of vessel shell, including pressure allowance. Default = 1.25 inches. include_platforms_ladders: whether to include platforms and ladders in costing, default = True. vessel_diameter: Pyomo component representing vessel diameter. If not provided, assumed to be named "diameter" vessel_length: Pyomo component representing vessel length. If not provided, assumed to be named "length". number_of_units: Integer or Pyomo component representing the number of parallel units to be costed, default = 1. """ SSLWCostingData.cost_vessel( blk, vertical=False, material_type=VesselMaterial.CarbonSteel, shell_thickness=1.25 * pyo.units.inch, weight_limit=1, include_platforms_ladders=True, vessel_diameter=None, vessel_length=None, number_of_units=1, )
[docs] def cost_fired_heater( blk, heat_source=HeaterSource.Fuel, material_type=HeaterMaterial.CarbonSteel, integer=True, ): """ Generic costing method for fired heaters. Args: heat_source: HeaterSource Enum indicating type of source of heat, default = HeaterSource.Fuel. material_type: HeaterMaterial Enum indicating material of construction, default = HeaterMaterial.CarbonSteel. integer: whether the number of units should be constrained to be an integer or not (default = True). """ # Validate arguments if heat_source not in HeaterSource: raise ConfigurationError( f"{blk.unit_model.name} received invalid argument for " f"heat_source: {heat_source}. Argument must be a member of " "the HeaterSource Enum." ) if material_type not in HeaterMaterial: raise ConfigurationError( f"{blk.unit_model.name} received invalid argument for " f"material_type: {material_type}. Argument must be a member " "of the HeaterMaterial Enum." ) # Build generic costing variables _make_common_vars(blk, integer) # Convert pressure to psi,g t0 = blk.unit_model.flowsheet().time.first() P = pyo.units.convert( blk.unit_model.control_volume.properties_in[t0].pressure, to_units=pyo.units.psi, ) - pyo.units.convert(1 * pyo.units.atm, to_units=pyo.units.psi) # Convert heat duty to BTU/hr Q = ( pyo.units.convert( blk.unit_model.heat_duty[t0], to_units=pyo.units.BTU / pyo.units.hr ) / blk.number_of_units ) # Material factor material_factor_dict = { HeaterMaterial.CarbonSteel: 1.0, HeaterMaterial.CrMoSteel: 1.4, HeaterMaterial.StainlessSteel: 1.7, } blk.material_factor = pyo.Param( initialize=material_factor_dict[material_type], domain=pyo.NonNegativeReals, doc="Construction material correction factor", ) # Pressure design factor calculation blk.pressure_factor = pyo.Var( initialize=1.1, domain=pyo.NonNegativeReals, doc="Pressure design factor" ) @blk.Constraint() def pressure_factor_eq(blk): return blk.pressure_factor == ( 0.986 - 0.0035 * (P / (500.00 * pyo.units.psi)) + 0.0175 * (P / (500.00 * pyo.units.psi)) ** 2 ) @blk.Constraint() def base_cost_per_unit_eq(blk): if heat_source == HeaterSource.Fuel: bc_expr = pyo.exp( 0.32325 + 0.766 * pyo.log(Q / pyo.units.BTU * pyo.units.hr) ) elif heat_source == HeaterSource.Reformer: bc_expr = 0.859 * (Q / pyo.units.BTU * pyo.units.hr) ** 0.81 elif heat_source == HeaterSource.Pyrolysis: bc_expr = 0.650 * (Q / pyo.units.BTU * pyo.units.hr) ** 0.81 elif heat_source == HeaterSource.HotWater: bc_expr = pyo.exp( 9.593 - 0.3769 * pyo.log((Q / pyo.units.BTU * pyo.units.hr)) + 0.03434 * pyo.log((Q / pyo.units.BTU * pyo.units.hr)) ** 2 ) elif heat_source == HeaterSource.Salts: bc_expr = 12.32 * (Q / pyo.units.BTU * pyo.units.hr) ** 0.64 elif heat_source == HeaterSource.DowthermA: bc_expr = 12.74 * (Q / pyo.units.BTU * pyo.units.hr) ** 0.65 elif heat_source == HeaterSource.steamBoiler: bc_expr = 0.367 * (Q / pyo.units.BTU * pyo.units.hr) ** 0.77 return blk.base_cost_per_unit == bc_expr * pyo.units.USD_CE500 @blk.Expression(doc="Base cost for all units installed") def base_cost(blk): return blk.base_cost_per_unit * blk.number_of_units # Total capital cost of heater(s) @blk.Constraint() def capital_cost_constraint(blk): return blk.capital_cost == ( blk.material_factor * blk.pressure_factor * blk.base_cost )
[docs] def cost_compressor( blk, compressor_type=CompressorType.Centrifugal, drive_type=CompressorDriveType.ElectricMotor, material_type=CompressorMaterial.StainlessSteel, integer=True, ): """ Generic costing method for compressors. Args: compressor_type: CompressorType Enum indicating type of type of equipment, default = CompressorType.Centrifugal. material_type: CompressorMaterial Enum indicating material of construction, default = CompressorMaterial.StainlessSteel. drive_type: CompressorDriveType Enum indicating type of type of drive to be used, default = CompressorDriveType.ElectricMotor. integer: whether the number of units should be constrained to be an integer or not (default = True). """ # Confirm that unit is a compressor if not blk.unit_model.config.compressor: _log.warning( "cost_compressor method is only appropriate for " "pressure changers with the compressor argument " "equal to True." ) # compressor = True, and using non-isentropic assumption # pumps should use the cost_pump method if ( blk.unit_model.config.thermodynamic_assumption == ThermodynamicAssumption.pump ): _log.warning( f"{blk.unit_model.name} - pressure changers with the pump " "assumption should use the cost_pump method." ) # isothermal or adiabatic compressors are too simple to cost elif blk.unit_model.config.thermodynamic_assumption in [ ThermodynamicAssumption.isothermal, ThermodynamicAssumption.adiabatic, ]: _log.warning( f"{blk.unit_model.name} - pressure changers without isentropic " "assumption are too simple to be costed." ) # Build generic costing variables _make_common_vars(blk, integer) work = blk.unit_model.work_mechanical[blk.unit_model.flowsheet().time.first()] work_hp = pyo.units.convert(work / blk.number_of_units, to_units=pyo.units.hp) # Compressor Purchase Cost Correlation FD_param = { CompressorDriveType.ElectricMotor: 1, CompressorDriveType.SteamTurbine: 1.15, CompressorDriveType.gasTurbine: 1.25, } blk.drive_factor = pyo.Param( mutable=True, initialize=FD_param[drive_type], doc="Mover drive factor" ) material_factor_dict = { CompressorMaterial.CarbonSteel: 1, CompressorMaterial.StainlessSteel: 2.5, CompressorMaterial.NickelAlloy: 5.0, } blk.material_factor = pyo.Param( mutable=True, initialize=material_factor_dict[material_type], doc="Material factor", ) alpha_dict = { CompressorType.Centrifugal: {1: 7.58, 2: 0.8}, CompressorType.Reciprocating: {1: 7.9661, 2: 0.8}, CompressorType.Screw: {1: 8.1238, 2: 0.7243}, } alpha = alpha_dict[compressor_type] # Purchase cost rule @blk.Constraint() def base_cost_per_unit_eq(blk): return blk.base_cost_per_unit == ( pyo.exp(alpha[1] + alpha[2] * pyo.log(work_hp / pyo.units.hp)) * pyo.units.USD_CE500 ) @blk.Expression(doc="Base cost for all units installed") def base_cost(blk): return blk.base_cost_per_unit * blk.number_of_units @blk.Constraint() def capital_cost_constraint(blk): return blk.capital_cost == ( blk.drive_factor * blk.material_factor * blk.base_cost )
[docs] def cost_fan( blk, fan_type=FanType.CentrifugalBackward, fan_head_factor=1.45, material_type=FanMaterial.StainlessSteel, integer=True, ): """ Generic costing method for fans. Args: fan_type: FanType Enum indicating type of type of equipment, default = FanType.CentrifugalBackward. fan_head_factor: (float) fan head factor (default=1.45). material_type: FanMaterial Enum indicating material of construction, default = FanMaterial.StainlessSteel. integer: whether the number of units should be constrained to be an integer or not (default = True). """ # Confirm that unit is a turbine if not blk.unit_model.config.compressor: raise TypeError( "cost_fan method is only appropriate for " "pressure changers with the compressor argument " "equal to True." ) # Build generic costing variables _make_common_vars(blk, integer) t0 = blk.unit_model.flowsheet().time.first() Q = blk.unit_model.control_volume.properties_in[t0].flow_vol Qcfm = pyo.units.convert( Q / blk.number_of_units, to_units=pyo.units.foot**3 / pyo.units.minute ) # Fan cost correlation alpha_dict = { FanType.CentrifugalBackward: {1: 11.0757, 2: 1.12906, 3: 0.08860}, FanType.CentrifugalStraight: {1: 12.1678, 2: 1.31363, 3: 0.09974}, FanType.VaneAxial: {1: 9.5229, 2: 0.97566, 3: 0.08532}, FanType.TubeAxial: {1: 6.12905, 2: 0.40254, 3: 0.05787}, } alpha = alpha_dict[fan_type] blk.head_factor = pyo.Param( initialize=fan_head_factor, mutable=True, doc="Fan head factor" ) material_factor_dict = { FanMaterial.CarbonSteel: 1.0, FanMaterial.Fiberglass: 1.8, FanMaterial.StainlessSteel: 2.5, FanMaterial.NickelAlloy: 5.0, } blk.material_factor = pyo.Param( initialize=material_factor_dict[material_type], mutable=True, doc="Material factor", ) # Base cost @blk.Constraint() def base_cost_per_unit_eq(blk): return blk.base_cost_per_unit == ( pyo.exp( alpha[1] - alpha[2] * pyo.log(Qcfm / (pyo.units.foot**3 / pyo.units.minute)) + alpha[3] * pyo.log(Qcfm / (pyo.units.foot**3 / pyo.units.minute)) ** 2 ) * pyo.units.USD_CE500 ) @blk.Expression(doc="Base cost for all units installed") def base_cost(blk): return blk.base_cost_per_unit * blk.number_of_units @blk.Constraint() def capital_cost_constraint(blk): return blk.capital_cost == ( blk.material_factor * blk.head_factor * blk.base_cost )
[docs] def cost_blower( blk, blower_type=BlowerType.Centrifugal, material_type=BlowerMaterial.StainlessSteel, integer=True, ): """ Generic costing method for blowers. Args: blower_type: BlowerType Enum indicating type of type of equipment, default = BlowerType.Centrifugal. material_type: BlowerMaterial Enum indicating material of construction, default = BlowerMaterial.StainlessSteel. integer: whether the number of units should be constrained to be an integer or not (default = True). """ # Confirm that unit is a turbine if not blk.unit_model.config.compressor: raise TypeError( "cost_blower method is only appropriate for " "pressure changers with the compressor argument " "equal to True." ) # Build generic costing variables _make_common_vars(blk, integer) t0 = blk.unit_model.flowsheet().time.first() work = blk.unit_model.work_mechanical[t0] work_hp = pyo.units.convert(work / blk.number_of_units, to_units=pyo.units.hp) # Fan cost correlation alpha_dict = { BlowerType.Centrifugal: {1: 6.8929, 2: 0.7900, 3: 0.0}, BlowerType.Rotary: {1: 7.59176, 2: 0.7932, 3: 0.012900}, } alpha = alpha_dict[blower_type] material_factor_dict = { BlowerMaterial.CarbonSteel: 1.0, BlowerMaterial.Aluminum: 0.60, BlowerMaterial.Fiberglass: 1.8, BlowerMaterial.StainlessSteel: 2.5, BlowerMaterial.NickelAlloy: 5.0, } blk.material_factor = pyo.Param( initialize=material_factor_dict[material_type], mutable=True, doc="Material factor", ) # Base cost @blk.Constraint() def base_cost_per_unit_eq(blk): return blk.base_cost_per_unit == ( pyo.exp( alpha[1] + alpha[2] * pyo.log(work_hp / pyo.units.hp) - alpha[3] * pyo.log(work_hp / pyo.units.hp) ** 2 ) * pyo.units.USD_CE500 ) @blk.Expression(doc="Base cost for all units installed") def base_cost(blk): return blk.base_cost_per_unit * blk.number_of_units @blk.Constraint() def capital_cost_constraint(blk): return blk.capital_cost == blk.material_factor * blk.base_cost
[docs] def cost_turbine(blk, integer=True): """ Generic costing method for turbines. Args: integer: whether the number of units should be constrained to be an integer or not (default = True). """ # Confirm that unit is a turbine if blk.unit_model.config.compressor: raise TypeError( "cost_turbine method is only appropriate for " "pressure changers with the compressor argument " "equal to False." ) # Build costing variables blk.capital_cost = pyo.Var( initialize=1e4, domain=pyo.NonNegativeReals, bounds=(0, None), units=pyo.units.USD_CE500, doc="Capital cost of all units", ) if integer is True: domain = pyo.Integers else: domain = pyo.NonNegativeReals blk.number_of_units = pyo.Var( initialize=1, domain=domain, bounds=(1, 100), doc="Number of units to install.", ) blk.number_of_units.fix(1) work = -blk.unit_model.work_mechanical[blk.unit_model.flowsheet().time.first()] work_hp = pyo.units.convert(work / blk.number_of_units, to_units=pyo.units.hp) @blk.Constraint() def capital_cost_constraint(blk): return blk.capital_cost == ( blk.number_of_units * 530 * (work_hp / pyo.units.hp) ** 0.81 * pyo.units.USD_CE500 )
[docs] def cost_pump( blk, pump_type=PumpType.Centrifugal, material_type=PumpMaterial.StainlessSteel, pump_type_factor=1.4, motor_type=PumpMotorType.Open, integer=True, ): """ Generic costing method for pumps. Args: pump_type: PumpType Enum indicating type of type of equipment, default = PumpType.Centrifugal. material_type: PumpMaterial Enum indicating material of construction, default = PumpMaterial.StainlessSteel. Material type is tied to PumpType. pump_type_factor: empirical factor for centrigual pumps based on table in source. Valid values are [1.1, 1.2, 1.3, 1.4 (default), 2.1, 2.2]. motor_type: PumpMotorType Enum indicating type of type of motor to be used, default = PumpMotorType.Open. integer: whether the number of units should be constrained to be an integer or not (default = True). """ # Confirm that unit is a pump/compressor if not blk.unit_model.config.compressor: raise TypeError( "cost_pump method is only appropriate for " "pressure changers with the compressor argument " "equal to True." ) # Validate argument combinations if pump_type == PumpType.Reciprocating: if material_type not in [ PumpMaterial.DuctileIron, PumpMaterial.NiAlBronze, PumpMaterial.CarbonSteel, PumpMaterial.StainlessSteel, ]: raise ConfigurationError( f"{blk.name} invalid combination of arguments. If " "pump_type == PumpType.Reciprocating then material_type " "must be one of PumpMaterial.ductileIron, " "PumpMaterial.NiAlBronze, PumpMaterial.CarbonSteel, or " "PumpMaterial.StainlessSteel." ) else: if material_type in [PumpMaterial.NiAlBronze, PumpMaterial.CarbonSteel]: raise ConfigurationError( f"{blk.name} invalid combination of arguments. " "PumpMaterial.NiAlBronze and " "PumpMaterial.CarbonSteel material_type" " are only valid for pump_type == PumpType.Reciprocating." ) # Add common variables blk.capital_cost = pyo.Var( initialize=1e4, domain=pyo.NonNegativeReals, bounds=(0, None), units=pyo.units.USD_CE500, doc="Capital cost of all units", ) if integer is True: domain = pyo.Integers else: domain = pyo.NonNegativeReals blk.number_of_units = pyo.Var( initialize=1, domain=domain, bounds=(1, 100), doc="Number of units to install.", ) blk.number_of_units.fix(1) t0 = blk.unit_model.flowsheet().time.first() work = pyo.units.convert( blk.unit_model.work_mechanical[t0] / blk.number_of_units, to_units=pyo.units.hp, ) # Pressure units required in lbf/ft^2 deltaP = pyo.units.convert( blk.unit_model.deltaP[t0], to_units=pyo.units.psi * pyo.units.inch**2 / pyo.units.foot**2, ) # Mass density units required in lb/ft^3 dens_mass = pyo.units.convert( blk.unit_model.control_volume.properties_in[t0].dens_mass, to_units=pyo.units.pound / pyo.units.foot**3, ) # Volumetric flow units required in gpm Q = pyo.units.convert( blk.unit_model.control_volume.properties_in[t0].flow_vol / blk.number_of_units, to_units=pyo.units.gallon / pyo.units.minute, ) # Calculate pump head blk.pump_head = pyo.Var( initialize=10, domain=pyo.NonNegativeReals, doc="Pump Head in feet of fluid flowing (Pressure rise/density)", units=pyo.units.pound_force * pyo.units.foot / pyo.units.pound, ) @blk.Constraint() def pump_head_eq(blk): return blk.pump_head == deltaP / dens_mass # Pump size factor: S = Q(H)**0.5 # Q = is the flow rate through the pump in gallons per minute # H = pump head in feet of flowing (pressure rise/liquid density) blk.size_factor = pyo.Var( initialize=10000, domain=pyo.NonNegativeReals, doc="Pump size factor, f(Q,pump_head)", ) @blk.Constraint() def size_factor_eq(blk): return blk.size_factor == ( Q * pyo.units.minute / pyo.units.gallon * ( blk.pump_head * pyo.units.pound / pyo.units.pound_force / pyo.units.foot ) ** 0.5 ) # Material factor if pump_type != PumpType.Reciprocating: material_factor_dict = { PumpMaterial.CastIron: 1.00, PumpMaterial.DuctileIron: 1.15, PumpMaterial.CastSteel: 1.35, PumpMaterial.Bronze: 1.90, PumpMaterial.StainlessSteel: 2.00, PumpMaterial.HastelloyC: 2.95, PumpMaterial.Monel: 3.30, PumpMaterial.Nickel: 3.50, PumpMaterial.Titanium: 9.70, } else: material_factor_dict = { PumpMaterial.DuctileIron: 1.00, PumpMaterial.StainlessSteel: 2.20, PumpMaterial.NiAlBronze: 1.15, PumpMaterial.CarbonSteel: 1.50, } blk.material_factor = pyo.Param( initialize=material_factor_dict[material_type], mutable=True, doc="Construction material correction factor", ) # Pump type factor if pump_type == PumpType.Centrifugal: pump_type_factor_dict = { 1.1: 1.00, 1.2: 1.50, 1.3: 1.70, 1.4: 2.00, 2.1: 2.70, 2.2: 8.90, } try: ptf = pump_type_factor_dict[pump_type_factor] except KeyError: raise ConfigurationError( f"{blk.name} invalid value for pump_type_factor argument: " f"{pump_type_factor}. Value must be one of [1.1, 1.2, 1.3," " 1.4, 2.1, 2.2]" ) else: ptf = 1 blk.FT = pyo.Param(mutable=True, initialize=ptf, doc="Pump type factor") # Base pump cost per unit blk.base_pump_cost_per_unit = pyo.Var( initialize=1e5, domain=pyo.NonNegativeReals, units=pyo.units.USD_CE394, doc="Base cost of pump (less motor) per unit", ) @blk.Constraint() def base_pump_cost_per_unit_eq(blk): if pump_type == PumpType.Centrifugal: bpc = pyo.exp( 9.7171 - 0.6019 * pyo.log(blk.size_factor) + 0.0519 * pyo.log(blk.size_factor) ** 2 ) elif pump_type == PumpType.ExternalGear: bpc = pyo.exp( 7.6964 + 0.1986 * pyo.log(Q / (pyo.units.gallon / pyo.units.minute)) + 0.0291 * pyo.log(Q / (pyo.units.gallon / pyo.units.minute)) ** 2 ) elif pump_type == PumpType.Reciprocating: bpc = pyo.exp( 7.8103 + 0.26986 * pyo.log(work / pyo.units.hp) + 0.06718 * pyo.log(work / pyo.units.hp) ** 2 ) return blk.base_pump_cost_per_unit == bpc * pyo.units.USD_CE394 @blk.Expression(doc="Base cost for all pumps (less motors)") def base_pump_cost(blk): return blk.base_pump_cost_per_unit * blk.number_of_units blk.pump_capital_cost = pyo.Var( initialize=100000, domain=pyo.NonNegativeReals, units=pyo.units.USD_CE500, doc="Capital cost of pumps (less motors)", ) @blk.Constraint() def pump_capital_cost_eq(blk): return blk.pump_capital_cost == pyo.units.convert( blk.FT * blk.material_factor * blk.base_pump_cost, to_units=pyo.units.USD_CE500, ) # Motor Costs pump_motor_type_dict = { PumpMotorType.Open: 1, PumpMotorType.Enclosed: 1.4, PumpMotorType.ExplosionProof: 1.8, } blk.motor_FT = pyo.Param( mutable=True, initialize=pump_motor_type_dict[motor_type], doc="Motor type factor", ) # Efficiency of the electric motor eta_m = ( 0.80 + 0.0319 * pyo.log(work / pyo.units.hp) - 0.00182 * pyo.log(work / pyo.units.hp) ** 2 ) work_motor = work / eta_m blk.base_motor_cost_per_unit = pyo.Var( initialize=10000, domain=pyo.NonNegativeReals, units=pyo.units.USD_CE394, doc="Motor base purchase cost per unit", ) @blk.Constraint() def base_motor_cost_eq(blk): return blk.base_motor_cost_per_unit == ( pyo.exp( 5.8259 + 0.13141 * pyo.log(work_motor / pyo.units.hp) + 0.053255 * pyo.log(work_motor / pyo.units.hp) ** 2 + 0.028628 * pyo.log(work_motor / pyo.units.hp) ** 3 - 0.0035549 * pyo.log(work_motor / pyo.units.hp) ** 4 ) * pyo.units.USD_CE394 ) @blk.Expression(doc="Base cost for all motors") def motor_base_cost(blk): return blk.base_motor_cost_per_unit * blk.number_of_units blk.motor_capital_cost = pyo.Var( initialize=100000, domain=pyo.NonNegativeReals, units=pyo.units.USD_CE500, doc="Capital cost of all motors", ) @blk.Constraint() def motor_capital_cost_eq(blk): return blk.motor_capital_cost == pyo.units.convert( blk.motor_FT * blk.motor_base_cost, to_units=pyo.units.USD_CE500 ) # Total capital cost (pump + electrical motor) @blk.Constraint() def capital_cost_constraint(blk): return blk.capital_cost == (blk.pump_capital_cost + blk.motor_capital_cost)
[docs] def cost_pressure_changer(blk, mover_type="compressor", **kwargs): """ Gateway method for costing pressure changers. This method attempts to determine the type of pressure changer from the compressor and thermodynamic assumption config arguments and then calls th appropriate sub-method. As such, keyword arguments to this method will depend on the sub-method called. Users are generally encouraged to call the specific sub-methods directly as required. Args: mover_type: optional argument to indicate type of pressure changer. """ if not blk.unit_model.config.compressor or mover_type == "turbine": # Unit is a turbine SSLWCostingData.cost_turbine(blk, **kwargs) elif mover_type == "compressor": # Unit is a pump SSLWCostingData.cost_compressor(blk, **kwargs) elif ( mover_type == "pump" or blk.unit_model.config.thermodynamic_assumption == ThermodynamicAssumption.pump ): # Unit is a pump SSLWCostingData.cost_pump(blk, **kwargs) elif mover_type == "blower": # Unit is a pump SSLWCostingData.cost_pump(blk, **kwargs) elif mover_type == "fan": # Unit is a pump SSLWCostingData.cost_fan(blk, **kwargs) else: raise ConfigurationError( f"{blk.name} - unrecognized value for mover_type argument: " f"{mover_type}." )
# ------------------------------------------------------------------------- # Map costing methods to unit model classes # Here we can provide a dict mapping unit model classes to costing methods # Even better, this is inheritance aware so e.g. Pump will first look for a # method assigned to Pump, and then fall back to PressureChanger unit_mapping = { CSTR: cost_vertical_vessel, Compressor: cost_compressor, Flash: cost_vertical_vessel, Heater: cost_fired_heater, HeatExchanger: cost_heat_exchanger, HeatExchangerNTU: cost_heat_exchanger, PFR: cost_horizontal_vessel, PressureChanger: cost_pressure_changer, Pump: cost_pump, StoichiometricReactor: cost_horizontal_vessel, Turbine: cost_turbine, }
# ----------------------------------------------------------------------------- def _make_common_vars(blk, integer=True): # Build generic costing variables (most costing models need these vars) blk.base_cost_per_unit = pyo.Var( initialize=1e5, domain=pyo.NonNegativeReals, units=pyo.units.USD_CE500, doc="Base cost per unit", ) blk.capital_cost = pyo.Var( initialize=1e4, domain=pyo.NonNegativeReals, bounds=(0, None), units=pyo.units.USD_CE500, doc="Capital cost of all units", ) if integer is True: domain = pyo.Integers else: domain = pyo.NonNegativeReals blk.number_of_units = pyo.Var( initialize=1, domain=domain, bounds=(1, 100), doc="Number of units to install." ) blk.number_of_units.fix(1)