Skip to content

Commit 8dc5787

Browse files
Dedicated us inputs scenarios (#181)
* code: introduce dedicated inputs and folders for the US * code: add manual input usa * code: latest changes to compile_cost_assumptions_usa.py * code: latest changes to compile_cost_assumptions_usa.py - 2 * code: relocate adjust_for_inflation to _helpers.py * code: update costs in manual_input.csv * code: next step on compile_cost_assumptions_usa.py * code: updates * code: add docstring * code: pre-commit * code: new updates to docstring * code: docstring * code: docstring in _helpers.py * docu: update the release notes * code: add unit tests * code: add pre-commit * code: sort outputs * code: add pre-commit * doc: add docstring * code: re-import numpy and pandas in _helpers.py * add scenarios for H2 and DAC (US only) + apply inflation rate to EUR only * doc: update release notes * code: fix unit tests * data: modify manual_input_usa.csv specifying financial_case R&D for rows where scenario is defined * add missing scenarios for electrolyzers * pre-commit changes * reorder manual_input_usa.csv * add new files * fix financial case for ICCT technologies * ensure that financial case is correctly reported in output files * update unit test * modify representative NREL technologies * include description of scenario and financial case * code: resolve FutureWarning from set_specify_assumptions --------- Co-authored-by: Fabrizio Finozzi <[email protected]> Co-authored-by: Fabrizio Finozzi <[email protected]>
1 parent e4ab832 commit 8dc5787

15 files changed

+24654
-24175
lines changed

config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ nrel_atb:
1313
nrel_atb_core_metric_parameter_to_keep: ["CAPEX", "CF", "Fixed O&M", "Variable O&M", "Fuel", "Additional OCC"]
1414
nrel_atb_technology_to_remove: ["Coal-CCS-95% -> Transformational Tech", "Coal-Max-CCS -> Transformational Tech", "Coal-new -> Transformational Tech", "NG combined cycle 95% CCS (F-frame basis -> Transformational Tech)", "NG combined cycle 95% CCS (H-frame basis -> Transformational Tech)", "NG combined cycle Max CCS (F-frame basis -> Transformational Tech)", "NG combined cycle Max CCS (H-frame basis -> Transformational Tech)", "AEO"]
1515
nrel_atb_source_link: "NREL/ATB-https://data.openei.org/s3_viewer?bucket=oedi-data-lake&prefix=ATB%2Felectricity%2Fcsv%2F2022%2F"
16+
nrel_atb_further_description: "Meaning of scenario and financial case: https://atb.nrel.gov/electricity/2024/definitions#scenarios"
1617

1718
expectation: "" # tech data uncertainty, possible options [None, "optimist", "pessimist"]
1819

docs/release_notes.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ Upcoming Release
2424

2525
* Align `snakemake` version and the related `mock_snakemake` to PyPSA-Eur (https://github.com/PyPSA/technology-data/pull/177)
2626

27+
* Improve filename consistency in the sources (https://github.com/PyPSA/technology-data/pull/178)
28+
2729
* Improve assumptions for iron-air batteries (https://github.com/PyPSA/technology-data/pull/179)
2830

29-
* Improve filename consistency in the sources (https://github.com/PyPSA/technology-data/pull/178)
31+
* US-specific scenarios for electrolyzers and DAC + adjustment for inflation removed as already considered in input data (https://github.com/PyPSA/technology-data/pull/181)
3032

3133
* Include further unit tests for compile_cost_assumptions_usa.py (https://github.com/PyPSA/technology-data/pull/182)
3234

docs/structure.rst

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ This repository has the following structure:
4949
* nrel_atb_core_metric_parameter_to_keep : list of parameters to use from the NREL/ATB source files
5050
* nrel_atb_technology_to_remove : list of technologies that should be excluded from NREL/ATB
5151
* nrel_atb_source_link : source url for the NREL/ATB data used
52+
* nrel_atb_further_description : Meaning of "scenario" and "financial case"
5253
* expectation : tech data uncertainty, possible options [None, "optimist", "pessimist"]
5354
* eur_year : year for EUR outputs
5455
* solar_utility_from_vartiaien : Bool (True/False) if solar utility data is taken from DEA or Vartiaien

inputs/US/discount_rates_usa.csv

+834-834
Large diffs are not rendered by default.

inputs/US/manual_input_usa.csv

+245-134
Large diffs are not rendered by default.

outputs/US/costs_2020.csv

+3,138-3,098
Large diffs are not rendered by default.

outputs/US/costs_2025.csv

+3,208-3,168
Large diffs are not rendered by default.

outputs/US/costs_2030.csv

+3,400-3,360
Large diffs are not rendered by default.

outputs/US/costs_2035.csv

+3,424-3,384
Large diffs are not rendered by default.

outputs/US/costs_2040.csv

+3,424-3,384
Large diffs are not rendered by default.

outputs/US/costs_2045.csv

+3,424-3,384
Large diffs are not rendered by default.

outputs/US/costs_2050.csv

+3,424-3,384
Large diffs are not rendered by default.

scripts/compile_cost_assumptions_usa.py

+109-31
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ def get_conversion_dictionary(flag: str) -> dict:
6060
"Nuclear - Large": "nuclear",
6161
"Nuclear - AP1000": "nuclear",
6262
"Geothermal - Hydro / Flash": "geothermal",
63-
"Land-Based Wind - Class 1": "onwind",
64-
"Land-Based Wind - Class 1 - Technology 1": "onwind",
65-
"Offshore Wind - Class 1": "offwind",
66-
"Utility PV - Class 1": "solar-utility",
63+
"Land-Based Wind - Class 4": "onwind",
64+
"Land-Based Wind - Class 4 - Technology 1": "onwind",
65+
"Offshore Wind - Class 3": "offwind",
66+
"Utility PV - Class 5": "solar-utility",
6767
"Commercial PV - Class 1": "solar-rooftop",
6868
"Utility-Scale Battery Storage - 6Hr": "battery storage",
6969
"Biopower": "biomass",
@@ -72,9 +72,9 @@ def get_conversion_dictionary(flag: str) -> dict:
7272
}
7373
elif flag.casefold() == "atb_technology_name":
7474
return {
75+
"Land-Based Wind - Class 1": "Land-Based Wind - Class 1 - Technology 1",
7576
"Land-Based Wind - Class 2": "Land-Based Wind - Class 2 - Technology 1",
7677
"Land-Based Wind - Class 3": "Land-Based Wind - Class 3 - Technology 1",
77-
"Land-Based Wind - Class 4": "Land-Based Wind - Class 4 - Technology 1",
7878
"Land-Based Wind - Class 5": "Land-Based Wind - Class 5 - Technology 1",
7979
"Land-Based Wind - Class 6": "Land-Based Wind - Class 6 - Technology 1",
8080
"Land-Based Wind - Class 7": "Land-Based Wind - Class 7 - Technology 1",
@@ -357,23 +357,65 @@ def pre_process_manual_input_usa(
357357
"technology == @tech and parameter == @param"
358358
)
359359

360-
s = pd.Series(
361-
index=list_of_years,
362-
data=np.interp(list_of_years, c["year"], c["value"]),
363-
name=param,
364-
)
365-
s["parameter"] = param
366-
s["technology"] = tech
367-
try:
368-
s["currency_year"] = int(c["currency_year"].values[0])
369-
except ValueError:
370-
s["currency_year"] = np.nan
371-
for col in ["unit", "source", "further description"]:
372-
s[col] = "; and\n".join(c[col].unique().astype(str))
373-
s = s.rename(
374-
{"further_description": "further description"}
375-
) # match column name between manual_input and original TD workflow
376-
list_dataframe_row.append(s)
360+
# Consider differences among scenarios
361+
scenarios = c["scenario"].dropna().unique()
362+
363+
for scenario in scenarios:
364+
scenario_value = c[c["scenario"] == scenario][
365+
"value"
366+
].values # Extract values for each scenario
367+
368+
if scenario_value.size > 0:
369+
scenario_years = c[c["scenario"] == scenario]["year"].values
370+
scenario_values = c[c["scenario"] == scenario]["value"].values
371+
372+
interpolated_values = np.interp(
373+
list_of_years, scenario_years, scenario_values
374+
)
375+
376+
# Create a row for each scenario
377+
s_copy = pd.Series(
378+
index=list_of_years,
379+
data=interpolated_values, # values are now interpolated
380+
name=param,
381+
)
382+
383+
s_copy["parameter"] = param
384+
s_copy["technology"] = tech
385+
s_copy["scenario"] = scenario
386+
try:
387+
s_copy["currency_year"] = int(c["currency_year"].values[0])
388+
except ValueError:
389+
s_copy["currency_year"] = np.nan
390+
391+
# Add the other columns in the data file
392+
for col in ["unit", "source", "further description"]:
393+
s_copy[col] = c[col].unique()[0]
394+
395+
# Add a separate row for each `financial_case`
396+
for financial_case in c["financial_case"].unique():
397+
s_copy["financial_case"] = financial_case
398+
list_dataframe_row.append(s_copy.copy())
399+
if len(scenarios) == 0:
400+
s = pd.Series(
401+
index=list_of_years,
402+
data=[scenario_value] * len(list_of_years),
403+
name=param,
404+
)
405+
s["parameter"] = param
406+
s["technology"] = tech
407+
s["scenario"] = ""
408+
try:
409+
s["currency_year"] = int(c["currency_year"].values[0])
410+
except ValueError:
411+
s["currency_year"] = np.nan
412+
for col in ["unit", "source", "further description"]:
413+
s[col] = c[col].unique()[0]
414+
415+
# Add a separate row for each `financial_case`
416+
for financial_case in c["financial_case"].unique():
417+
s["financial_case"] = financial_case
418+
list_dataframe_row.append(s.copy())
377419
manual_input_usa_file_df = pd.DataFrame(list_dataframe_row).reset_index(drop=True)
378420

379421
# Filter the information for a given year
@@ -386,25 +428,56 @@ def pre_process_manual_input_usa(
386428
"source",
387429
"further description",
388430
"currency_year",
431+
"financial_case",
432+
"scenario",
389433
]
390434
].rename(columns={year: "value"})
391435

436+
# Filter data to get technologies with scenario differentiation
437+
with_scenario_df = manual_input_usa_file_df[
438+
manual_input_usa_file_df["scenario"].notna()
439+
]
440+
without_scenario_df = manual_input_usa_file_df[
441+
manual_input_usa_file_df["scenario"].isna()
442+
]
443+
444+
final_rows = []
445+
446+
for tech in manual_input_usa_file_df["technology"].unique():
447+
tech_with_scenario = with_scenario_df[with_scenario_df["technology"] == tech]
448+
if len(tech_with_scenario) > 0:
449+
# Keep rows where a scenario exists
450+
final_rows.append(tech_with_scenario)
451+
else:
452+
# If a scenario is not defined, keep the row without scenario
453+
tech_without_scenario = without_scenario_df[
454+
without_scenario_df["technology"] == tech
455+
]
456+
final_rows.append(tech_without_scenario)
457+
458+
manual_input_usa_file_df = pd.concat(final_rows, ignore_index=True)
459+
392460
# Cast the value column to float
393461
manual_input_usa_file_df["value"] = manual_input_usa_file_df["value"].astype(float)
394462

395463
# Correct the cost assumptions to the inflation rate
396-
inflation_adjusted_manual_input_usa_file_df = adjust_for_inflation(
397-
inflation_rate_series,
398-
manual_input_usa_file_df,
399-
manual_input_usa_file_df.technology.unique(),
400-
eur_year,
401-
"value",
402-
usa_costs_flag=True,
464+
mask = manual_input_usa_file_df["unit"].str.startswith("EUR", na=False)
465+
466+
inflation_adjusted_manual_input_usa_file_df = manual_input_usa_file_df.copy()
467+
inflation_adjusted_manual_input_usa_file_df.loc[mask, "value"] = (
468+
adjust_for_inflation(
469+
inflation_rate_series,
470+
manual_input_usa_file_df.loc[mask],
471+
manual_input_usa_file_df.loc[mask, "technology"].unique(),
472+
eur_year,
473+
"value",
474+
usa_costs_flag=True,
475+
)["value"]
403476
)
404477

405478
# Round the results
406479
inflation_adjusted_manual_input_usa_file_df.loc[:, "value"] = round(
407-
inflation_adjusted_manual_input_usa_file_df.value.astype(float), n_digits
480+
inflation_adjusted_manual_input_usa_file_df["value"].astype(float), n_digits
408481
)
409482

410483
return inflation_adjusted_manual_input_usa_file_df
@@ -688,6 +761,7 @@ def pre_process_cost_input_file(
688761
def pre_process_atb_input_file(
689762
input_file_path: str,
690763
nrel_source: str,
764+
nrel_further_description: str,
691765
year: int,
692766
list_columns_to_keep: list,
693767
list_core_metric_parameter_to_keep: list,
@@ -803,7 +877,7 @@ def pre_process_atb_input_file(
803877
atb_input_df["source"] = nrel_source
804878

805879
# Add further description column
806-
atb_input_df["further description"] = pd.Series(dtype="str")
880+
atb_input_df["further description"] = nrel_further_description
807881

808882
# Rename columns and select just columns used in PyPSA
809883
column_rename_dict = get_conversion_dictionary("output_column")
@@ -923,6 +997,9 @@ def duplicate_fuel_cost(input_file_path: str, list_of_years: list) -> pd.DataFra
923997
"nrel_atb_core_metric_parameter_to_keep"
924998
]
925999
nrel_atb_source_link = snakemake.config["nrel_atb"]["nrel_atb_source_link"]
1000+
nrel_atb_further_description = snakemake.config["nrel_atb"][
1001+
"nrel_atb_further_description"
1002+
]
9261003
nrel_atb_technology_to_remove = snakemake.config["nrel_atb"][
9271004
"nrel_atb_technology_to_remove"
9281005
]
@@ -989,6 +1066,7 @@ def duplicate_fuel_cost(input_file_path: str, list_of_years: list) -> pd.DataFra
9891066
atb_e_df = pre_process_atb_input_file(
9901067
input_atb_path,
9911068
nrel_atb_source_link,
1069+
nrel_atb_further_description,
9921070
year_val,
9931071
nrel_atb_columns_to_keep,
9941072
nrel_atb_core_metric_parameter_to_keep,

test/test_compile_cost_assumptions.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ def test_set_specify_assumptions():
288288
"decentral gas boiler",
289289
"decentral gas boiler",
290290
"biogas upgrading",
291+
"biogas upgrading",
291292
"solar-rooftop",
292293
"heat pump",
293294
],
@@ -297,34 +298,37 @@ def test_set_specify_assumptions():
297298
"Possible additional specific investment",
298299
"Technical lifetime",
299300
"investment",
301+
"Technical lifetime",
300302
"PV module conversion efficiency [p.u.]",
301303
"Heat efficiency, annual average, net, radiators",
302304
],
303-
"2020": [1.0] * 7,
304-
"source": ["source"] * 7,
305-
"unit": ["unit"] * 7,
305+
"2020": [1.0] * 8,
306+
"source": ["source"] * 8,
307+
"unit": ["unit"] * 8,
306308
}
307309
).set_index(["technology", "parameter"])
308310

309311
reference_output_df = pd.DataFrame(
310312
{
311313
"technology": [
314+
"biogas upgrading",
312315
"biogas upgrading",
313316
"decentral gas boiler",
314317
"decentral gas boiler connection",
315318
"decentral gas boiler connection",
316319
"heat pump",
317320
],
318321
"parameter": [
322+
"Technical lifetime",
319323
"investment (upgrading, methane redution and grid injection)",
320324
"Technical lifetime",
321325
"Possible additional specific investment",
322326
"Technical lifetime",
323327
"Heat efficiency, annual average, net, radiators, existing one family house",
324328
],
325-
"2020": [1.0, 1.0, 1.0, 50.0, 1.0],
326-
"source": ["source"] * 5,
327-
"unit": ["unit"] * 5,
329+
"2020": [1.0, 1.0, 1.0, 1.0, 50.0, 1.0],
330+
"source": ["source"] * 6,
331+
"unit": ["unit"] * 6,
328332
}
329333
)
330334
list_of_years = ["2020"]

test/test_compile_cost_assumptions_usa.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,11 @@ def test_pre_process_atb_input_file(config, input_file_year, year, expected):
253253
]
254254
nrel_atb_technology_to_remove = config["nrel_atb"]["nrel_atb_technology_to_remove"]
255255
nrel_atb_source_link = config["nrel_atb"]["nrel_atb_source_link"]
256+
nrel_atb_further_description = config["nrel_atb"]["nrel_atb_further_description"]
256257
output_df = pre_process_atb_input_file(
257258
input_file_path,
258259
nrel_atb_source_link,
260+
nrel_atb_further_description,
259261
year,
260262
nrel_atb_columns_to_keep,
261263
nrel_atb_core_metric_parameter_to_keep,
@@ -379,13 +381,13 @@ def test_duplicate_fuel_cost(config):
379381
@pytest.mark.parametrize(
380382
"year, expected",
381383
[
382-
(2020, (91, 7)),
383-
(2025, (91, 7)),
384-
(2030, (91, 7)),
385-
(2035, (91, 7)),
386-
(2040, (91, 7)),
387-
(2045, (91, 7)),
388-
(2050, (91, 7)),
384+
(2020, (130, 9)),
385+
(2025, (130, 9)),
386+
(2030, (130, 9)),
387+
(2035, (130, 9)),
388+
(2040, (130, 9)),
389+
(2045, (130, 9)),
390+
(2050, (130, 9)),
389391
],
390392
)
391393
def test_pre_process_manual_input_usa(config, year, expected):

0 commit comments

Comments
 (0)