diff --git a/idaes/core/base/costing_base.py b/idaes/core/base/costing_base.py index a7ad809ccd..d143c09aaa 100644 --- a/idaes/core/base/costing_base.py +++ b/idaes/core/base/costing_base.py @@ -19,10 +19,14 @@ # This plays with some private attributes - most are necessary # pylint: disable=protected-access +import json +import os + from functools import partial import pyomo.environ as pyo from pyomo.common.config import ConfigBlock, ConfigValue +from pyomo.common.fileutils import this_file_dir from pyomo.util.calc_var_value import calculate_variable_from_constraint from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr @@ -98,6 +102,41 @@ def register_idaes_currency_units(): ) +def load_location_factor(): + """ + Estimate the cost of constructing the same plant in different global locations using location factors. + + This method uses a location (or investment site) factor to adjust the total permanent investment (TPI) + based on regional differences in labor costs, workforce efficiency, local regulations and customs, + union status, and other local economic conditions. + + Reference: + Seider, Warren D., et al. *Product and Process Design Principles: Synthesis, Analysis, and Evaluation.* + John Wiley & Sons, 2016. + + The conversion equation is given by: + + .. math:: + C_{TPI, corrected} = F_{ISF} \times C_{TPI} + + where: + - :math:`C` represents cost, + - :math:`F` represents a factor, + - :math:`TPI` is the total plant investment, and + - :math:`ISF` is the investment site factor (i.e., location factor). + + Location factors for 139 countries are sourced from Compass International (2017): + https://www.compassinternational.net/wp-content/uploads/2017/01/Worldwide-Industrial.pdf + + Note: For some countries, multiple city-specific location factors are provided. + The benchmark location is Washington, D.C., USA. + """ + directory = this_file_dir() + with open(os.path.join(directory, "location_factors.json"), "r") as file: + location_factors = json.load(file) + return location_factors + + class DefaultCostingComponents(StrEnum): """ Costing components Enum @@ -169,6 +208,9 @@ def build(self): self.base_currency = None self.base_period = pyo.units.year + # Set the location factor + self.location_factor = ("United States", "Washington DC") + # Register unit mapping self._costing_methods_map = {} self._build_costing_methods_map() diff --git a/idaes/core/base/location_factors.json b/idaes/core/base/location_factors.json new file mode 100644 index 0000000000..f0f2be13a4 --- /dev/null +++ b/idaes/core/base/location_factors.json @@ -0,0 +1,1694 @@ +[ + { + "country": "Afghanistan", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.96, + "average": 0.935 + } + }, + { + "country": "Albania", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.93, + "average": 0.91 + } + }, + { + "country": "Algeria", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Angola", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.97, + "average": 0.945 + } + }, + { + "country": "Argentina", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.95, + "average": 0.935 + } + }, + { + "country": "Armenia", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.94, + "average": 0.92 + } + }, + { + "country": "Australia", + "city": "Melbourne", + "location_factor": { + "min": 1.02, + "max": 1.02, + "average": 1.02 + } + }, + { + "country": "Australia", + "city": "Perth", + "location_factor": { + "min": 1.03, + "max": 1.03, + "average": 1.03 + } + }, + { + "country": "Australia", + "city": "Sydney", + "location_factor": { + "min": 1.03, + "max": 1.03, + "average": 1.03 + } + }, + { + "country": "Austria", + "city": null, + "location_factor": { + "min": 1.04, + "max": 1.04, + "average": 1.04 + } + }, + { + "country": "Azerbaijan", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.97, + "average": 0.945 + } + }, + { + "country": "Bahrain", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.95, + "average": 0.935 + } + }, + { + "country": "Bangladesh", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.92, + "average": 0.91 + } + }, + { + "country": "Belarus", + "city": null, + "location_factor": { + "min": 0.93, + "max": 0.96, + "average": 0.945 + } + }, + { + "country": "Belgium", + "city": null, + "location_factor": { + "min": 1.01, + "max": 1.04, + "average": 1.025 + } + }, + { + "country": "Belize", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.95, + "average": 0.93 + } + }, + { + "country": "Benin", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Bhutan", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Bolivia", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.94, + "average": 0.92 + } + }, + { + "country": "Bosnia and Herzegovina", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Botswana", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Brazil", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.96, + "average": 0.92 + } + }, + { + "country": "Bulgaria", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Burkina Faso", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.95, + "average": 0.915 + } + }, + { + "country": "Burundi", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.95, + "average": 0.915 + } + }, + { + "country": "Cambodia", + "city": null, + "location_factor": { + "min": 0.84, + "max": 0.94, + "average": 0.89 + } + }, + { + "country": "Cameroon", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Canada", + "city": "Calgary AB", + "location_factor": { + "min": 1.05, + "max": 1.1, + "average": 1.075 + } + }, + { + "country": "Canada", + "city": "Charlottetown PEI", + "location_factor": { + "min": 0.96, + "max": 0.99, + "average": 0.975 + } + }, + { + "country": "Canada", + "city": "Edmonton AB", + "location_factor": { + "min": 1.04, + "max": 1.13, + "average": 1.085 + } + }, + { + "country": "Canada", + "city": "Fort McMurray AB", + "location_factor": { + "min": 1.4, + "max": 1.55, + "average": 1.475 + } + }, + { + "country": "Canada", + "city": "Halifax NS", + "location_factor": { + "min": 0.96, + "max": 0.99, + "average": 0.975 + } + }, + { + "country": "Canada", + "city": "Hamilton ON", + "location_factor": { + "min": 0.95, + "max": 0.98, + "average": 0.965 + } + }, + { + "country": "Canada", + "city": "Laval QC", + "location_factor": { + "min": 0.95, + "max": 0.97, + "average": 0.96 + } + }, + { + "country": "Canada", + "city": "Lethbridge AB", + "location_factor": { + "min": 0.97, + "max": 0.99, + "average": 0.98 + } + }, + { + "country": "Canada", + "city": "London ONT", + "location_factor": { + "min": 0.95, + "max": 0.98, + "average": 0.965 + } + }, + { + "country": "Canada", + "city": "Moncton NB", + "location_factor": { + "min": 0.94, + "max": 0.97, + "average": 0.955 + } + }, + { + "country": "Canada", + "city": "Montreal QC", + "location_factor": { + "min": 0.96, + "max": 0.98, + "average": 0.97 + } + }, + { + "country": "Canada", + "city": "Québec City QC", + "location_factor": { + "min": 0.96, + "max": 0.98, + "average": 0.97 + } + }, + { + "country": "Canada", + "city": "Regina SK", + "location_factor": { + "min": 0.96, + "max": 0.98, + "average": 0.97 + } + }, + { + "country": "Canada", + "city": "Sarnia ON", + "location_factor": { + "min": 0.95, + "max": 0.98, + "average": 0.965 + } + }, + { + "country": "Canada", + "city": "St John's NL", + "location_factor": { + "min": 0.96, + "max": 0.98, + "average": 0.97 + } + }, + { + "country": "Canada", + "city": "Toronto ON", + "location_factor": { + "min": 0.97, + "max": 0.99, + "average": 0.98 + } + }, + { + "country": "Canada", + "city": "Whitehorse YT", + "location_factor": { + "min": 0.98, + "max": 1.00, + "average": 0.99 + } + }, + { + "country": "Canada", + "city": "Winnipeg MB", + "location_factor": { + "min": 0.95, + "max": 0.97, + "average": 0.96 + } + }, + { + "country": "Canada", + "city": "Windsor ON", + "location_factor": { + "min": 0.95, + "max": 0.98, + "average": 0.965 + } + }, + { + "country": "Canada", + "city": "Vancouver BC", + "location_factor": { + "min": 1.0, + "max": 1.03, + "average": 1.015 + } + }, + { + "country": "Canada", + "city": "Victoria BC", + "location_factor": { + "min": 0.97, + "max": 1.0, + "average": 0.985 + } + }, + { + "country": "Central African Republic", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.94, + "average": 0.92 + } + }, + { + "country": "Chad", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.93, + "average": 0.91 + } + }, + { + "country": "Chile", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.95, + "average": 0.93 + } + }, + { + "country": "China", + "city": "Beijing", + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "China", + "city": "Chengdu", + "location_factor": { + "min": 0.88, + "max": 0.93, + "average": 0.905 + } + }, + { + "country": "China", + "city": "Harbin", + "location_factor": { + "min": 0.88, + "max": 0.93, + "average": 0.905 + } + }, + { + "country": "China", + "city": "Guangzhou", + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "China", + "city": "Shanghai", + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Colombia", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.93, + "average": 0.915 + } + }, + { + "country": "Democratic Republic of the Congo", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.94, + "average": 0.92 + } + }, + { + "country": "Costa Rica", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.94, + "average": 0.93 + } + }, + { + "country": "Côte d'Ivoire", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.93, + "average": 0.915 + } + }, + { + "country": "Croatia", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Cuba", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.94, + "average": 0.93 + } + }, + { + "country": "Cyprus", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.94, + "average": 0.93 + } + }, + { + "country": "Czech Republic", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.93, + "average": 0.91 + } + }, + { + "country": "Denmark", + "city": null, + "location_factor": { + "min": 1.05, + "max": 1.09, + "average": 1.07 + } + }, + { + "country": "Dominican Republic", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.95, + "average": 0.935 + } + }, + { + "country": "Ecuador", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.94, + "average": 0.925 + } + }, + { + "country": "Egypt", + "city": null, + "location_factor": { + "min": 0.86, + "max": 0.94, + "average": 0.9 + } + }, + { + "country": "El Salvador", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.94, + "average": 0.93 + } + }, + { + "country": "Eritrea", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.93, + "average": 0.905 + } + }, + { + "country": "Estonia", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.94, + "average": 0.92 + } + }, + { + "country": "Ethiopia", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.93, + "average": 0.91 + } + }, + { + "country": "Finland", + "city": null, + "location_factor": { + "min": 1.06, + "max": 1.1, + "average": 1.08 + } + }, + { + "country": "France", + "city": "Bordeaux", + "location_factor": { + "min": 0.97, + "max": 1.02, + "average": 0.995 + } + }, + { + "country": "France", + "city": "Lyon", + "location_factor": { + "min": 0.97, + "max": 1.03, + "average": 1.0 + } + }, + { + "country": "France", + "city": "Marseille / Fos", + "location_factor": { + "min": 0.97, + "max": 1.01, + "average": 0.99 + } + }, + { + "country": "France", + "city": "Nice", + "location_factor": { + "min": 0.98, + "max": 1.02, + "average": 1.0 + } + }, + { + "country": "France", + "city": "Paris", + "location_factor": { + "min": 1.0, + "max": 1.05, + "average": 1.025 + } + }, + { + "country": "Gabon", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.94, + "average": 0.915 + } + }, + { + "country": "The Gambia", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.93, + "average": 0.91 + } + }, + { + "country": "Georgia", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.95, + "average": 0.935 + } + }, + { + "country": "Germany", + "city": "Berlin", + "location_factor": { + "min": 1.01, + "max": 1.04, + "average": 1.025 + } + }, + { + "country": "Germany", + "city": "Hamburg", + "location_factor": { + "min": 1.01, + "max": 1.04, + "average": 1.025 + } + }, + { + "country": "Germany", + "city": "Munich", + "location_factor": { + "min": 1.02, + "max": 1.05, + "average": 1.035 + } + }, + { + "country": "Ghana", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.94, + "average": 0.915 + } + }, + { + "country": "Greece", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.93, + "average": 0.92 + } + }, + { + "country": "Guatemala", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.94, + "average": 0.925 + } + }, + { + "country": "Guinea-Bissau", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.93, + "average": 0.915 + } + }, + { + "country": "Guinea", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.92, + "average": 0.9 + } + }, + { + "country": "Guyana", + "city": null, + "location_factor": { + "min": 0.85, + "max": 0.94, + "average": 0.895 + } + }, + { + "country": "Haiti", + "city": null, + "location_factor": { + "min": 0.87, + "max": 0.93, + "average": 0.9 + } + }, + { + "country": "Honduras", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.93, + "average": 0.915 + } + }, + { + "country": "Hong Kong", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.93, + "average": 0.915 + } + }, + { + "country": "Hungary", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.92, + "average": 0.9 + } + }, + { + "country": "India", + "city": null, + "location_factor": { + "min": 0.85, + "max": 0.92, + "average": 0.885 + } + }, + { + "country": "Indonesia", + "city": null, + "location_factor": { + "min": 0.75, + "max": 0.86, + "average": 0.805 + } + }, + { + "country": "Iran", + "city": null, + "location_factor": { + "min": 0.87, + "max": 0.93, + "average": 0.9 + } + }, + { + "country": "Iraq", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.96, + "average": 0.92 + } + }, + { + "country": "Ireland", + "city": "Cork", + "location_factor": { + "min": 0.95, + "max": 1.02, + "average": 0.985 + } + }, + { + "country": "Ireland", + "city": "Dublin", + "location_factor": { + "min": 0.95, + "max": 1.03, + "average": 0.99 + } + }, + { + "country": "Ireland", + "city": "Galway", + "location_factor": { + "min": 0.95, + "max": 1.0, + "average": 0.975 + } + }, + { + "country": "Israel", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.94, + "average": 0.93 + } + }, + { + "country": "Italy", + "city": "Bari", + "location_factor": { + "min": 0.93, + "max": 0.96, + "average": 0.945 + } + }, + { + "country": "Italy", + "city": "Milan", + "location_factor": { + "min": 0.94, + "max": 0.99, + "average": 0.965 + } + }, + { + "country": "Italy", + "city": "Rome", + "location_factor": { + "min": 0.94, + "max": 0.98, + "average": 0.96 + } + }, + { + "country": "Jamaica", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.93, + "average": 0.91 + } + }, + { + "country": "Japan", + "city": "Osaka", + "location_factor": { + "min": 1.05, + "max": 1.07, + "average": 1.06 + } + }, + { + "country": "Japan", + "city": "Tokyo", + "location_factor": { + "min": 1.06, + "max": 1.11, + "average": 1.085 + } + }, + { + "country": "Japan", + "city": "Yokohama", + "location_factor": { + "min": 1.05, + "max": 1.07, + "average": 1.06 + } + }, + { + "country": "Jordan", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.92, + "average": 0.9 + } + }, + { + "country": "Kazakhstan", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.96, + "average": 0.925 + } + }, + { + "country": "Kenya", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Kyrgyzstan", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.93, + "average": 0.915 + } + }, + { + "country": "Kuwait", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.97, + "average": 0.945 + } + }, + { + "country": "Laos", + "city": null, + "location_factor": { + "min": 0.83, + "max": 0.94, + "average": 0.885 + } + }, + { + "country": "Latvia", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.95, + "average": 0.935 + } + }, + { + "country": "Lebanon", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Libya", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.94, + "average": 0.925 + } + }, + { + "country": "Lithuania", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.96, + "average": 0.935 + } + }, + { + "country": "Madagascar", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.94, + "average": 0.91 + } + }, + { + "country": "Malawi", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.93, + "average": 0.91 + } + }, + { + "country": "Malaysia", + "city": null, + "location_factor": { + "min": 0.83, + "max": 0.92, + "average": 0.875 + } + }, + { + "country": "Mali", + "city": null, + "location_factor": { + "min": 0.87, + "max": 0.94, + "average": 0.905 + } + }, + { + "country": "Malta", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Mexico", + "city": null, + "location_factor": { + "min": 0.84, + "max": 0.92, + "average": 0.88 + } + }, + { + "country": "Mongolia", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.93, + "average": 0.91 + } + }, + { + "country": "Morocco", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.95, + "average": 0.92 + } + }, + { + "country": "Mozambique", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.94, + "average": 0.91 + } + }, + { + "country": "Myanmar", + "city": null, + "location_factor": { + "min": 0.85, + "max": 0.93, + "average": 0.89 + } + }, + { + "country": "Namibia", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.94, + "average": 0.91 + } + }, + { + "country": "Nepal", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.94, + "average": 0.915 + } + }, + { + "country": "The Netherlands", + "city": null, + "location_factor": { + "min": 1.0, + "max": 1.05, + "average": 1.025 + } + }, + { + "country": "New Zealand", + "city": null, + "location_factor": { + "min": 1.01, + "max": 1.04, + "average": 1.025 + } + }, + { + "country": "Nicaragua", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.94, + "average": 0.915 + } + }, + { + "country": "Niger", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.93, + "average": 0.905 + } + }, + { + "country": "Nigeria", + "city": null, + "location_factor": { + "min": 0.93, + "max": 0.98, + "average": 0.955 + } + }, + { + "country": "Norway", + "city": "Bergen", + "location_factor": { + "min": 1.1, + "max": 1.16, + "average": 1.13 + } + }, + { + "country": "Norway", + "city": "Oslo", + "location_factor": { + "min": 1.12, + "max": 1.17, + "average": 1.145 + } + }, + { + "country": "Norway", + "city": "Stavanger", + "location_factor": { + "min": 1.1, + "max": 1.16, + "average": 1.13 + } + }, + { + "country": "Oman", + "city": null, + "location_factor": { + "min": 0.93, + "max": 0.98, + "average": 0.955 + } + }, + { + "country": "Panama", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.93, + "average": 0.91 + } + }, + { + "country": "Pakistan", + "city": null, + "location_factor": { + "min": 0.87, + "max": 0.93, + "average": 0.9 + } + }, + { + "country": "Paraguay", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.94, + "average": 0.915 + } + }, + { + "country": "Peru", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.95, + "average": 0.92 + } + }, + { + "country": "Philippines", + "city": null, + "location_factor": { + "min": 0.86, + "max": 0.93, + "average": 0.895 + } + }, + { + "country": "Poland", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.94, + "average": 0.925 + } + }, + { + "country": "Portugal", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.93, + "average": 0.91 + } + }, + { + "country": "Romania", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.94, + "average": 0.915 + } + }, + { + "country": "Russia", + "city": "Moscow", + "location_factor": { + "min": 0.94, + "max": 0.97, + "average": 0.955 + } + }, + { + "country": "Russia", + "city": "Nizhnyi Novgorod", + "location_factor": { + "min": 0.92, + "max": 0.95, + "average": 0.935 + } + }, + { + "country": "Russia", + "city": "St Petersburg", + "location_factor": { + "min": 0.92, + "max": 0.96, + "average": 0.94 + } + }, + { + "country": "Russia", + "city": "Vladivostok", + "location_factor": { + "min": 0.92, + "max": 0.96, + "average": 0.94 + } + }, + { + "country": "Rwanda", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.93, + "average": 0.905 + } + }, + { + "country": "Saudi Arabia", + "city": null, + "location_factor": { + "min": 0.93, + "max": 0.98, + "average": 0.955 + } + }, + { + "country": "Senegal", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.94, + "average": 0.91 + } + }, + { + "country": "Singapore", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "South Africa", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.95, + "average": 0.935 + } + }, + { + "country": "South Korea", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.96, + "average": 0.94 + } + }, + { + "country": "Spain", + "city": null, + "location_factor": { + "min": 0.92, + "max": 0.94, + "average": 0.93 + } + }, + { + "country": "Sri Lanka", + "city": null, + "location_factor": { + "min": 0.78, + "max": 0.89, + "average": 0.835 + } + }, + { + "country": "Sudan", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.95, + "average": 0.915 + } + }, + { + "country": "Sweden", + "city": null, + "location_factor": { + "min": 1.07, + "max": 1.13, + "average": 1.1 + } + }, + { + "country": "Switzerland", + "city": null, + "location_factor": { + "min": 1.07, + "max": 1.14, + "average": 1.105 + } + }, + { + "country": "Syria", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.95, + "average": 0.93 + } + }, + { + "country": "Tajikistan", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.94, + "average": 0.915 + } + }, + { + "country": "Taiwan", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.94, + "average": 0.92 + } + }, + { + "country": "Thailand", + "city": null, + "location_factor": { + "min": 0.85, + "max": 0.93, + "average": 0.89 + } + }, + { + "country": "Togo", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.94, + "average": 0.915 + } + }, + { + "country": "Tunisia", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.95, + "average": 0.92 + } + }, + { + "country": "Turkey", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.94, + "average": 0.915 + } + }, + { + "country": "UAE", + "city": null, + "location_factor": { + "min": 0.93, + "max": 0.98, + "average": 0.955 + } + }, + { + "country": "Uganda", + "city": null, + "location_factor": { + "min": 0.9, + "max": 0.95, + "average": 0.925 + } + }, + { + "country": "Ukraine", + "city": null, + "location_factor": { + "min": 0.91, + "max": 0.95, + "average": 0.93 + } + }, + { + "country": "United Kingdom", + "city": "Birmingham", + "location_factor": { + "min": 1.03, + "max": 1.06, + "average": 1.045 + } + }, + { + "country": "United Kingdom", + "city": "Cambridge", + "location_factor": { + "min": 1.03, + "max": 1.07, + "average": 1.05 + } + }, + { + "country": "United Kingdom", + "city": "Edinburgh", + "location_factor": { + "min": 1.03, + "max": 1.06, + "average": 1.045 + } + }, + { + "country": "United Kingdom", + "city": "Liverpool / Speke", + "location_factor": { + "min": 1.02, + "max": 1.05, + "average": 1.035 + } + }, + { + "country": "United Kingdom", + "city": "London", + "location_factor": { + "min": 1.06, + "max": 1.11, + "average": 1.085 + } + }, + { + "country": "United Kingdom", + "city": "Manchester", + "location_factor": { + "min": 1.03, + "max": 1.07, + "average": 1.05 + } + }, + { + "country": "United Kingdom", + "city": "Oxford", + "location_factor": { + "min": 1.03, + "max": 1.07, + "average": 1.05 + } + }, + { + "country": "United States", + "city": "Washington DC", + "location_factor": { + "min": 1.0, + "max": 1.0, + "average": 1.0 + } + }, + { + "country": "Uruguay", + "city": null, + "location_factor": { + "min": 0.93, + "max": 0.95, + "average": 0.94 + } + }, + { + "country": "Uzbekistan", + "city": null, + "location_factor": { + "min": 0.93, + "max": 0.96, + "average": 0.945 + } + }, + { + "country": "Venezuela", + "city": null, + "location_factor": { + "min": 0.93, + "max": 0.96, + "average": 0.945 + } + }, + { + "country": "Vietnam", + "city": null, + "location_factor": { + "min": 0.86, + "max": 0.9, + "average": 0.88 + } + }, + { + "country": "Yemen", + "city": null, + "location_factor": { + "min": 0.89, + "max": 0.95, + "average": 0.92 + } + }, + { + "country": "Zambia", + "city": null, + "location_factor": { + "min": 0.88, + "max": 0.94, + "average": 0.91 + } + }, + { + "country": "Zimbabwe", + "city": null, + "location_factor": { + "min": 0.87, + "max": 0.93, + "average": 0.9 + } + } +] \ No newline at end of file diff --git a/idaes/core/base/tests/test_costing_base.py b/idaes/core/base/tests/test_costing_base.py index ab4533e7e8..bb60d72102 100644 --- a/idaes/core/base/tests/test_costing_base.py +++ b/idaes/core/base/tests/test_costing_base.py @@ -28,6 +28,8 @@ register_idaes_currency_units, ) +from idaes.core.base.costing_base import load_location_factor + # TODO : Tests for cases with multiple costing packages pyunits.load_definitions_from_strings(["USD_test = [test_currency]"]) @@ -84,6 +86,70 @@ def test_register_idaes_currency_units(): ) +@pytest.mark.unit +def test_all_valid_locations_have_factors(): + location_data = load_location_factor() + for entry in location_data: + location = (entry["country"], entry["city"]) + factor = entry.get("location_factor", {}).get("average") + assert factor is not None, f"No factor found for location: {location}" + assert isinstance( + factor, (int, float) + ), f"Invalid factor type for {location}: {factor}" + + +@pytest.mark.unit +def test_invalid_country_raises_keyerror(): + location_data = load_location_factor() + valid_locations = {(entry["country"], entry["city"]) for entry in location_data} + invalid_country = ("Brunei", None) + assert ( + invalid_country not in valid_locations + ), f"Test assumption failed: {invalid_country} unexpectedly exists in data" + + +@pytest.mark.unit +def test_fallback_to_country_without_city(): + location_data = load_location_factor() + test_location = ("Austria", "Vienna") + fallback_location = ("Austria", None) + + available_locations = {(entry["country"], entry["city"]) for entry in location_data} + if ( + fallback_location in available_locations + and test_location not in available_locations + ): + fallback_factor = next( + entry["location_factor"]["average"] + for entry in location_data + if (entry["country"], entry["city"]) == fallback_location + ) + assert isinstance(fallback_factor, (int, float)) + else: + pytest.skip("Test conditions not met for fallback check.") + + +@pytest.mark.unit +def test_invalid_city_without_fallback_raises_keyerror(): + location_data = load_location_factor() + test_location = ("United States", "Boston") + locations = {(entry["country"], entry["city"]) for entry in location_data} + + country_exists = any(loc[0] == test_location[0] for loc in locations) + no_city_match = test_location not in locations + no_country_fallback = (test_location[0], None) not in locations + + if country_exists and no_city_match and no_country_fallback: + with pytest.raises(StopIteration): + _ = next( + entry + for entry in location_data + if (entry["country"], entry["city"]) == test_location + ) + else: + pytest.skip("Test assumptions not satisfied for invalid city test.") + + class TestFlowsheetCostingBlock: @pytest.mark.unit def test_basic_attributes(self): diff --git a/idaes/models/costing/SSLW.py b/idaes/models/costing/SSLW.py index 3bde0af207..ed45a42f22 100644 --- a/idaes/models/costing/SSLW.py +++ b/idaes/models/costing/SSLW.py @@ -55,6 +55,8 @@ register_idaes_currency_units, ) +from idaes.core.base.costing_base import load_location_factor + import idaes.logger as idaeslog _log = idaeslog.getLogger(__name__) @@ -208,6 +210,7 @@ class BlowerMaterial(StrEnum): class SSLWCostingData(FlowsheetCostingBlockData): # Register currency and conversion rates based on CE Index register_idaes_currency_units() + load_location_factor() def build_global_params(self): """ @@ -222,6 +225,8 @@ def build_global_params(self): self.base_currency = pyo.units.USD_2018 # Set a base period for all operating costs self.base_period = pyo.units.year + # Chose location and set location factor + self.location_factor = ("United States", "Washington DC") def build_process_costs(self): """