Skip to content

Location factor - #1642

Open
Lingyan90 wants to merge 16 commits into
mainfrom
location_factor
Open

Location factor#1642
Lingyan90 wants to merge 16 commits into
mainfrom
location_factor

Conversation

@Lingyan90

@Lingyan90 Lingyan90 commented Jul 24, 2025

Copy link
Copy Markdown
Contributor

Fixes

The location factor is an important metric for analyzing different market location. Here, move the location factor from PrOMMiS to IDAES for broader application. (This originally started in PrOMMiS in PR prommis/prommis#143)

Summary/Motivation:

Move the location factor to IDAES so that other repository such as PrOMMiS, WaterTAP, etc. could also access these values.

Changes proposed in this PR:

  • Add location factor data in costing_base.py
  • Added location factor data bank into location_factors.jason
  • Added testing code in test_costing_base.py
  • Added location factor in SSLW.py

Legal Acknowledgement

By contributing to this software project, I agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the license terms described in the LICENSE.txt file at the top level of this directory.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

@codecov

codecov Bot commented Jul 25, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.57%. Comparing base (a60fbf8) to head (7813709).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1642      +/-   ##
==========================================
+ Coverage   77.56%   77.57%   +0.01%     
==========================================
  Files         423      423              
  Lines       67529    67565      +36     
  Branches    11420    11429       +9     
==========================================
+ Hits        52379    52415      +36     
  Misses      12531    12531              
  Partials     2619     2619              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dallan-keylogic dallan-keylogic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some questions about the implementation here.

Comment on lines +143 to +148
with pytest.raises(StopIteration):
_ = next(
entry
for entry in location_data
if (entry["country"], entry["city"]) == test_location
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test name indicates that this should raise a KeyError but the test itself indicates it raises a StopIteration error. It seems like you should catch the StopIteration error and raise a KeyError yourself.

Also, why are we iterating through a list entry-by-entry to check whether the country-city pair exists? Doesn't it make more sense to implement it an object (like a dict) that uses a hash lookup?

Comment on lines +149 to +150
else:
pytest.skip("Test assumptions not satisfied for invalid city test.")

@dallan-keylogic dallan-keylogic Jul 25, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we skipping the test here? The only reason we should be skipping tests is if the test requires some dependency (like a solver) that not every environment has.

@bpaul4 bpaul4 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Lingyan90 for opening this, I have a few comments.

Comment thread idaes/core/base/costing_base.py Outdated

def load_location_factor():
"""
Estimate the cost of constructing the same plant in different global locations using location factors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Estimate the cost of constructing the same plant in different global locations using location factors.
Estimate the cost of constructing the same plant in different regions using location factors.

Comment thread idaes/core/base/costing_base.py Outdated
)


def load_location_factor():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def load_location_factor():
def load_location_factors():

Comment thread idaes/core/base/costing_base.py Outdated
self.base_currency = None
self.base_period = pyo.units.year

# Set the location factor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Set the location factor
# Set the location factor, default U.S. Washington DC, location_factor = 1.00

register_idaes_currency_units,
)

from idaes.core.base.costing_base import load_location_factor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from idaes.core.base.costing_base import load_location_factor
from idaes.core.base.costing_base import load_location_factors


@pytest.mark.unit
def test_all_valid_locations_have_factors():
location_data = load_location_factor()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And everywhere else in the file where this appears.

Suggested change
location_data = load_location_factor()
location_data = load_location_factors()

), f"Test assumption failed: {invalid_country} unexpectedly exists in data"


@pytest.mark.unit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would this work in practice? If a user tried to call a country which an invalid region/city, would it return a default?

Comment thread idaes/models/costing/SSLW.py Outdated
register_idaes_currency_units,
)

from idaes.core.base.costing_base import load_location_factor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from idaes.core.base.costing_base import load_location_factor
from idaes.core.base.costing_base import load_location_factors

Comment thread idaes/models/costing/SSLW.py Outdated
class SSLWCostingData(FlowsheetCostingBlockData):
# Register currency and conversion rates based on CE Index
register_idaes_currency_units()
load_location_factor()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be assigned to an object, so it can be referenced later.

I think "location" should also be a configuration argument like "CE_index_year", so the UnitModelCostingBlock will save the attribute self.location = ("United States", "Washington DC") and then apply it to the capital cost equation.

Finally, please add tests to the SSLW test file applying location factors and check built objects, object values, and cost results.

Comment thread idaes/models/costing/SSLW.py Outdated
# 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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.location_factor = ("United States", "Washington DC")
self.location = ("United States", "Washington DC")

And then internal to the costing method, we can define self.location_factor = location_data[self.location].

@ksbeattie ksbeattie added the PrOMMiS Issues related to PrOMMiS activities label Aug 4, 2025
@ksbeattie ksbeattie mentioned this pull request Aug 4, 2025
4 tasks
@ksbeattie ksbeattie added the Priority:Normal Normal Priority Issue or PR label Aug 7, 2025
@ksbeattie

Copy link
Copy Markdown
Member

@Lingyan90, any news on this?

@Lingyan90

Copy link
Copy Markdown
Contributor Author

@Lingyan90, any news on this?

This PR should be addressed once the PrOMMiS costing dictionary has been moved to IDAES.

@ksbeattie

Copy link
Copy Markdown
Member

@Lingyan90, any news on this?

This PR should be addressed once the PrOMMiS costing dictionary has been moved to IDAES.

Is there a PR, issue and/or person for that move (in either PrOMMiS or IDAES)?

@bpaul4

bpaul4 commented Oct 9, 2025

Copy link
Copy Markdown
Contributor

@Lingyan90, any news on this?

This PR should be addressed once the PrOMMiS costing dictionary has been moved to IDAES.

Is there a PR, issue and/or person for that move (in either PrOMMiS or IDAES)?

@ksbeattie I and others on the costing team will coordinate this move soon. Here is the issue: prommis/prommis#150

@ksbeattie
ksbeattie enabled auto-merge (squash) November 24, 2025 17:53
@ksbeattie
ksbeattie disabled auto-merge November 24, 2025 17:53
@bpaul4 bpaul4 mentioned this pull request Jul 13, 2026
8 tasks
@bpaul4
bpaul4 requested a review from Akintomiwaojo July 28, 2026 20:56

@Lingyan90 Lingyan90 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bpaul4, The code looks very good to me.

One issue that worth discussion is if the base cost in not in USD, it's in CAD for example, and user want use that base CAD cost to convert to Finland EUR, then the location factor application should be something like TPC_finland = TPC_Canada * LocationFactor_finland / LocationFactor_CAD.

For more general use, I would suggest modify the current location application equation to something like above, e.g. TPC_finland = TPC_baselocation * LocationFactor_finland / LocationFactor_baselocation.

And would also worth clarify that the current database are based on USD, if user use other currency, they should be consistent with their referenced currency.

@Akintomiwaojo Akintomiwaojo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments and clarifications in-line.

Comment thread idaes/models/costing/QGESS.py Outdated
# city lists within countries are not too long, list them here for the user
raise KeyError(
f"City {city} not supported for country {country}; valid cities include "
f"{self.location_factor_dictionary[country].keys()}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dict_keys([...]) may appear in the message output. This might be an alternative:

f"{list(self.location_factor_dictionary[country].keys())}"

Comment thread idaes/models/costing/QGESS.py Outdated
# too many countries to list, point users to the data file
raise KeyError(
f"Country {country} not supported; please check the data file at "
f"IDAES.idaes-pse.idaes.core.base.locations_factors.json for spelling and supported countries."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
f"IDAES.idaes-pse.idaes.core.base.locations_factors.json for spelling and supported countries."
f"IDAES.idaes-pse.idaes.core.base.location_factors.json for spelling and supported countries."

- :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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please check this link again? It appears to be unavailable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please check this link again? It appears to be unavailable.

Is it in-house data that you requested? I checked www.compassinternational.net, and it looks like I need to request it.

@bpaul4 bpaul4 Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lingyan90 could you please comment on this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They probably have removed it or replaced it with different file. Could change the citation to something like this: Compass International, Inc. (2017). 2017 worldwide industrial [Sample edition]. https://www.compassinternational.net/wp-content/uploads/2017/01/Worldwide-Industrial.pdf (Accessed March 25, 2025).

are converted appropriately assuming the base factor of 1.0 corresponds to Washington D.C. and U.S. Northeast.

Note: For some countries, multiple city-specific location factors are provided.
The benchmark location is Washington, D.C., USA.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For clarification, does this imply that the benchmark for country factors, pulled from Compass International, is also Washington, D.C., USA? If not, we might be erroneously mixing factors with different benchmarks

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The factors from Compass International have a benchmark of 1.0 for Washington, D.C., USA and everything else is in relation to that. The regional US factors from Seider were scaled so that Northeast (which is assumed to be the same region as DC) is 1.0.

@bpaul4

bpaul4 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

@bpaul4, The code looks very good to me.

One issue that worth discussion is if the base cost in not in USD, it's in CAD for example, and user want use that base CAD cost to convert to Finland EUR, then the location factor application should be something like TPC_finland = TPC_Canada * LocationFactor_finland / LocationFactor_CAD.

For more general use, I would suggest modify the current location application equation to something like above, e.g. TPC_finland = TPC_baselocation * LocationFactor_finland / LocationFactor_baselocation.

And would also worth clarify that the current database are based on USD, if user use other currency, they should be consistent with their referenced currency.

At the moment we only support USD for currencies. Users could define their own, however the current costing framework assumes that currencies are of the form "USD" + some descriptor about the index value or year/month so it would require an additional option. As it is we don't have any reference costs in other currencies so we can keep this in mind as a potential expansion for future work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority:Normal Normal Priority Issue or PR PrOMMiS Issues related to PrOMMiS activities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants