From 844b0b68b220d2f639b96f1db5c4525bacb5a3a1 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:26:46 -0500 Subject: [PATCH 01/11] test case aleaf --- examples/1App_ALAEF_LCGTEP/Fuel.txt | 19 ++++++ examples/1App_ALAEF_LCGTEP/README.md | 21 +++++++ examples/1App_ALAEF_LCGTEP/watts_exec.py | 58 +++++++++++++++++ src/watts/__init__.py | 1 + src/watts/plugin_aleaf.py | 79 ++++++++++++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 examples/1App_ALAEF_LCGTEP/Fuel.txt create mode 100644 examples/1App_ALAEF_LCGTEP/README.md create mode 100644 examples/1App_ALAEF_LCGTEP/watts_exec.py create mode 100644 src/watts/plugin_aleaf.py diff --git a/examples/1App_ALAEF_LCGTEP/Fuel.txt b/examples/1App_ALAEF_LCGTEP/Fuel.txt new file mode 100644 index 0000000..66d190c --- /dev/null +++ b/examples/1App_ALAEF_LCGTEP/Fuel.txt @@ -0,0 +1,19 @@ +Scenario FUEL Type Unit 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 +Reference Coal Real 2021 $/MMBtu 1.64 1.61 1.65 1.57 1.52 1.50 1.49 1.50 1.50 1.50 1.50 1.50 1.51 1.52 1.53 1.54 1.56 1.56 1.58 1.59 1.60 1.60 1.62 1.63 1.64 1.65 1.65 1.66 1.66 1.75 1.85 1.96 2.07 2.19 2.31 2.44 2.58 2.73 2.89 +Reference NG Real 2021 $/MMBtu 3.84 3.49 3.17 3.00 2.98 3.08 3.25 3.36 3.46 3.54 3.58 3.65 3.64 3.64 3.65 3.67 3.68 3.69 3.72 3.73 3.70 3.71 3.65 3.61 3.60 3.60 3.62 3.60 3.59 3.80 4.01 4.24 4.48 4.74 5.01 5.29 5.59 5.91 6.25 +Reference Petroleum Real 2021 $/MMBtu 12.18 10.50 11.39 11.61 11.89 12.20 12.46 12.58 12.82 13.08 13.27 13.42 13.54 13.67 13.86 14.03 14.20 14.26 14.55 14.70 14.75 15.00 15.28 15.36 15.58 15.65 15.62 15.65 15.59 16.48 17.42 18.41 19.46 20.57 21.74 22.98 24.29 25.68 27.14 +Reference Nuclear Real 2021 $/MMBtu 0.72 0.72 0.72 0.72 0.72 0.73 0.73 0.73 0.73 0.73 0.73 0.74 0.74 0.74 0.74 0.74 0.74 0.75 0.75 0.75 0.75 0.76 0.76 0.76 0.76 0.76 0.77 0.77 0.77 0.81 0.86 0.91 0.96 1.02 1.07 1.14 1.20 1.27 1.34 +Reference Biomass Real 2020 $/MMBTU 5.00 5.11 5.22 5.34 5.45 5.57 5.70 5.82 5.95 6.08 6.22 6.35 6.49 6.63 6.78 6.93 7.08 7.24 7.40 7.56 7.73 7.90 8.07 8.25 8.43 8.61 8.80 9.00 9.20 9.72 10.27 10.86 11.48 12.13 12.82 13.56 14.33 15.15 16.01 +Reference OGS Real 2020 $/MMBTU 3.84 3.49 3.17 3.00 2.98 3.08 3.25 3.36 3.46 3.54 3.58 3.65 3.64 3.64 3.65 3.67 3.68 3.69 3.72 3.73 3.70 3.71 3.65 3.61 3.60 3.60 3.62 3.60 3.59 3.80 4.01 4.24 4.48 4.74 5.01 5.29 5.59 5.91 6.25 +Base Coal Real 2020 $/MMBTU 1.57 1.54 1.57 1.50 1.45 1.43 1.43 1.43 1.43 1.44 1.43 1.43 1.44 1.45 1.46 1.47 1.49 1.49 1.51 1.52 1.52 1.53 1.54 1.56 1.56 1.57 1.57 1.58 1.58 1.67 1.77 1.87 1.98 2.09 2.21 2.33 2.47 2.61 2.76 +Base NG Real 2020 $/MMBTU 3.67 3.34 3.03 2.87 2.84 2.94 3.10 3.21 3.30 3.38 3.42 3.48 3.48 3.47 3.48 3.50 3.52 3.52 3.55 3.56 3.54 3.54 3.48 3.45 3.44 3.43 3.46 3.44 3.43 3.62 3.83 4.05 4.28 4.52 4.78 5.05 5.34 5.65 5.97 +Base Petroleum Real 2020 $/MMBTU 11.63 10.03 10.88 11.09 11.35 11.65 11.90 12.01 12.24 12.49 12.68 12.82 12.93 13.05 13.24 13.40 13.56 13.61 13.90 14.04 14.09 14.32 14.60 14.67 14.88 14.95 14.92 14.94 14.89 15.74 16.63 17.58 18.58 19.64 20.76 21.95 23.20 24.52 25.92 +Base Nuclear Real 2020 $/MMBTU {{fuel_2022}} {{fuel_2023}} {{fuel_2024}} {{fuel_2025}} {{fuel_2026}} {{fuel_2027}} {{fuel_2028}} {{fuel_2029}} {{fuel_2030}} {{fuel_2031}} {{fuel_2032}} {{fuel_2033}} {{fuel_2034}} {{fuel_2035}} {{fuel_2036}} {{fuel_2037}} {{fuel_2038}} {{fuel_2039}} {{fuel_2040}} {{fuel_2041}} {{fuel_2042}} {{fuel_2043}} {{fuel_2044}} {{fuel_2045}} {{fuel_2046}} {{fuel_2047}} {{fuel_2048}} {{fuel_2049}} {{fuel_2050}} {{fuel_2051}} {{fuel_2052}} {{fuel_2053}} {{fuel_2054}} {{fuel_2055}} {{fuel_2056}} {{fuel_2057}} {{fuel_2058}} {{fuel_2059}} {{fuel_2060}} +Base Biomass Real 2020 $/MMBTU 5.00 5.11 5.22 5.34 5.45 5.57 5.70 5.82 5.95 6.08 6.22 6.35 6.49 6.63 6.78 6.93 7.08 7.24 7.40 7.56 7.73 7.90 8.07 8.25 8.43 8.61 8.80 9.00 9.20 9.72 10.27 10.86 11.48 12.13 12.82 13.56 14.33 15.15 16.01 +Base OGS Real 2020 $/MMBTU 3.67 3.34 3.03 2.87 2.84 2.94 3.10 3.21 3.30 3.38 3.42 3.48 3.48 3.47 3.48 3.50 3.52 3.52 3.55 3.56 3.54 3.54 3.48 3.45 3.44 3.43 3.46 3.44 3.43 3.62 3.83 4.05 4.28 4.52 4.78 5.05 5.34 5.65 5.97 +Low Coal Real 2020 $/MMBTU 1.41 1.38 1.42 1.35 1.31 1.29 1.28 1.29 1.29 1.29 1.29 1.29 1.30 1.30 1.32 1.33 1.34 1.34 1.36 1.37 1.37 1.38 1.39 1.40 1.41 1.42 1.41 1.42 1.43 1.51 1.59 1.68 1.78 1.88 1.99 2.10 2.22 2.35 2.48 +Low NG Real 2020 $/MMBTU 3.30 3.00 2.73 2.58 2.56 2.64 2.79 2.89 2.97 3.05 3.07 3.13 3.13 3.13 3.14 3.15 3.17 3.17 3.20 3.20 3.18 3.19 3.13 3.11 3.10 3.09 3.11 3.09 3.09 3.26 3.45 3.64 3.85 4.07 4.30 4.55 4.81 5.08 5.37 +Low Petroleum Real 2020 $/MMBTU 10.46 9.03 9.79 9.98 10.22 10.48 10.71 10.81 11.02 11.24 11.41 11.53 11.64 11.75 11.91 12.06 12.21 12.25 12.51 12.63 12.68 12.89 13.14 13.21 13.39 13.45 13.43 13.45 13.40 14.16 14.97 15.82 16.73 17.68 18.69 19.75 20.88 22.07 23.33 +Low Nuclear Real 2020 $/MMBTU 0.62 0.62 0.62 0.62 0.62 0.62 0.62 0.63 0.63 0.63 0.63 0.63 0.63 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 1.03 1.09 1.15 +Low Biomass Real 2020 $/MMBTU 4.50 4.60 4.70 4.80 4.91 5.02 5.13 5.24 5.36 5.47 5.59 5.72 5.84 5.97 6.10 6.24 6.37 6.51 6.66 6.80 6.95 7.11 7.26 7.42 7.59 7.75 7.92 8.10 8.28 8.75 9.25 9.77 10.33 10.92 11.54 12.20 12.90 13.63 14.41 +Low OGS Real 2020 $/MMBTU 3.30 3.00 2.73 2.58 2.56 2.64 2.79 2.89 2.97 3.05 3.07 3.13 3.13 3.13 3.14 3.15 3.17 3.17 3.20 3.20 3.18 3.19 3.13 3.11 3.10 3.09 3.11 3.09 3.09 3.26 3.45 3.64 3.85 4.07 4.30 4.55 4.81 5.08 5.37 \ No newline at end of file diff --git a/examples/1App_ALAEF_LCGTEP/README.md b/examples/1App_ALAEF_LCGTEP/README.md new file mode 100644 index 0000000..656a1e9 --- /dev/null +++ b/examples/1App_ALAEF_LCGTEP/README.md @@ -0,0 +1,21 @@ +# 1App_ALEAF_LCGTEP + +## Purpose + +This example provides a demonstration for using WATTS to run and analyze simulations with A-LEAF (Argonne Low-carbon Electricity Analysis Framework) under various supply chain disruption scenarios. The example explores different fuel price settings to understand the impact on US nuclear capacity expansion result. + + +## Code(s) + +- A-LEAF +- Julia (A-LEAF dependency) + +## Keywords + +- Energy Market +- Economic Modeling + +## File descriptions + +- [__watts_exec.py__](watts_exec.py): WATTS workflow for this example. This is the file to execute to run the problem described above. +- [__gcmat_template__](Fuel.txt): Fuel price for this example diff --git a/examples/1App_ALAEF_LCGTEP/watts_exec.py b/examples/1App_ALAEF_LCGTEP/watts_exec.py new file mode 100644 index 0000000..5cd1816 --- /dev/null +++ b/examples/1App_ALAEF_LCGTEP/watts_exec.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2022-2023 UChicago Argonne, LLC +# SPDX-License-Identifier: MIT + +""" +This example demonstrates how to use WATTS to run an A-LEAF calculation. +""" + +import watts +from pathlib import Path +import numpy as np +import time + +params = watts.Parameters() + + +reference_prices = { + 2022: 0.62, 2023: 0.62, 2024: 0.62, 2025: 0.62, 2026: 0.62, 2027: 0.62, + 2028: 0.63, 2029: 0.63, 2030: 0.63, 2031: 0.76, 2032: 0.76, 2033: 0.76, + 2034: 0.76, 2035: 0.76, 2036: 0.77, 2037: 0.77, 2038: 0.77, 2039: 0.77, + 2040: 0.77, 2041: 0.78, 2042: 0.78, 2043: 0.78, 2044: 0.78, 2045: 0.78, + 2046: 0.79, 2047: 0.79, 2048: 0.79, 2049: 0.79, 2050: 0.79, 2051: 0.80, + 2052: 0.80, 2053: 0.80, 2054: 0.80, 2055: 0.80, 2056: 0.81, 2057: 0.81, + 2058: 0.81, 2059: 0.81, 2060: 0.81 +} + + +# Set nuclear prices from reference and calculated growth +starting_year = 2031 +starting_price = 0.76 +# Set nuclear prices from reference up to 2030 +for year in range(2022, starting_year): + params[f'fuel_{year}'] = reference_prices[year] + +# Assign the starting price for 2031 +params[f'fuel_{starting_year}'] = starting_price + +# Compute prices from 2031 onwards using the growth rates +for year in range(starting_year + 1, 2061): + prev_year_price = params[f'fuel_{year - 1}'] + growth_factor = 1.0028 if year <= 2050 else 1.057 + params[f'fuel_{year}'] = round(prev_year_price * growth_factor, 3) + +params.show_summary(show_metadata=True, sort_by='key') + +# Set default path for results +results_path = Path.cwd() / 'results' +results_path.mkdir(exist_ok=True, parents=True) +watts.Database.set_default_path(results_path) + +# Create ALEAF plugin +# aleaf_plugin = watts.PluginALEAF('Fuel.txt', extra_templates={'Simulation Configuration': 'Simulation Configuration.txt'}) +aleaf_plugin = watts.PluginALEAF('Fuel.txt') +# Run ALEAF +aleaf_result = aleaf_plugin(params) +print('ALEAF simulation completed.') + +# Collect and display results +print(aleaf_result.csv_data) diff --git a/src/watts/__init__.py b/src/watts/__init__.py index 83c0ada..838f4a4 100644 --- a/src/watts/__init__.py +++ b/src/watts/__init__.py @@ -13,6 +13,7 @@ from .plugin_abce import * from .plugin_dakota import * from .plugin_gcmat import * +from .plugin_aleaf import * from .results import * from .template import * from .parameters import * diff --git a/src/watts/plugin_aleaf.py b/src/watts/plugin_aleaf.py new file mode 100644 index 0000000..77ff024 --- /dev/null +++ b/src/watts/plugin_aleaf.py @@ -0,0 +1,79 @@ +from pathlib import Path +import shutil +import pandas as pd +from typing import List, Optional +import os + +from .plugin import Plugin +from .results import Results, ExecInfo +from .fileutils import PathLike +from .parameters import Parameters +from .template import TemplateRenderer +import subprocess + + + +class ResultsALEAF(Results): + """ALEAF simulation results.""" + + def __init__(self, params: Parameters, exec_info: ExecInfo, + inputs: List[PathLike], outputs: List[PathLike]): + super().__init__(params, exec_info, inputs, outputs) + self.csv_data = self._get_aleaf_csv_data() + + def _get_aleaf_csv_data(self) -> pd.DataFrame: + """Read ALEAF output CSV file and return results as a DataFrame.""" + output_file = next((p for p in self.outputs if p.name.endswith('_system_tech_summary_EXP.csv')), None) + if output_file and output_file.exists(): + return pd.read_csv(output_file) + else: + return pd.DataFrame() # Return an empty DataFrame if no CSV file is found + + +class PluginALEAF(Plugin): + """Plugin for running ALEAF.""" + + def __init__(self, template_file: PathLike, extra_templates: Optional[dict] = None, + show_stdout: bool = False, show_stderr: bool = False): + super().__init__([], show_stdout, show_stderr) + self.template_file = template_file + self.extra_templates = extra_templates or {} + self.plugin_name = 'ALEAF' + self.renderer = TemplateRenderer(self.template_file) + self.aleaf_dir = os.getenv('ALEAF_DIR') + if not self.aleaf_dir: + raise EnvironmentError("ALEAF_DIR environment variable is not set.") + + def prerun(self, params: Parameters) -> None: + """Generate ALEAF input files.""" + + # Copy the original ALEAF input file + original_input_path = Path(self.aleaf_dir) / "setting/ALEAF_Master_LC_GTEP.xlsx" + modified_input_path = Path("ALEAF_Master_LC_GTEP.xlsx") + shutil.copy(original_input_path, modified_input_path) + + # Load the Excel file and modify the 'Fuel' sheet + fuel_data = self.renderer(params) + + with pd.ExcelWriter(modified_input_path, engine='openpyxl', mode='a', if_sheet_exists='overlay') as writer: + fuel_sheet = pd.read_excel(writer, sheet_name='Fuel') + fuel_sheet.update(fuel_data) + fuel_sheet.to_excel(writer, sheet_name='Fuel', index=False) + + # Render and apply additional templates if provided + for sheet_name, template_path in self.extra_templates.items(): + template_renderer = TemplateRenderer(template_path) + sheet_data = template_renderer(params) + with pd.ExcelWriter(modified_input_path, engine='openpyxl', mode='a', if_sheet_exists='overlay') as writer: + sheet_data.to_excel(writer, sheet_name=sheet_name, index=False) + + def run(self): + """Run ALEAF.""" + command = ['julia', 'execute_ALEAF.jl'] + subprocess.run(command, cwd=self.aleaf_dir) + + def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsALEAF: + """Collect information from ALEAF simulation and create results object.""" + output_folder = Path(self.aleaf_dir) / f"output/LC_GTEP/USA/case_id_1_Test EXP" + outputs = [output_folder / "Test EXP__system_tech_summary_EXP.csv"] + return ResultsALEAF(params, exec_info, self.extra_inputs, outputs) From 14c8872c68f23a137e22441b5bb7c715c620a57b Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:13:12 -0500 Subject: [PATCH 02/11] overlay to replace in pandas --- src/watts/plugin_aleaf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/watts/plugin_aleaf.py b/src/watts/plugin_aleaf.py index 77ff024..f3c706a 100644 --- a/src/watts/plugin_aleaf.py +++ b/src/watts/plugin_aleaf.py @@ -55,7 +55,7 @@ def prerun(self, params: Parameters) -> None: # Load the Excel file and modify the 'Fuel' sheet fuel_data = self.renderer(params) - with pd.ExcelWriter(modified_input_path, engine='openpyxl', mode='a', if_sheet_exists='overlay') as writer: + with pd.ExcelWriter(modified_input_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer: fuel_sheet = pd.read_excel(writer, sheet_name='Fuel') fuel_sheet.update(fuel_data) fuel_sheet.to_excel(writer, sheet_name='Fuel', index=False) @@ -64,7 +64,7 @@ def prerun(self, params: Parameters) -> None: for sheet_name, template_path in self.extra_templates.items(): template_renderer = TemplateRenderer(template_path) sheet_data = template_renderer(params) - with pd.ExcelWriter(modified_input_path, engine='openpyxl', mode='a', if_sheet_exists='overlay') as writer: + with pd.ExcelWriter(modified_input_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer: sheet_data.to_excel(writer, sheet_name=sheet_name, index=False) def run(self): From 5a59ab5720fb37f88c6a7d1cc1d51f4e7ef6234d Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 28 Aug 2024 15:33:47 -0500 Subject: [PATCH 03/11] add prerun --- src/watts/plugin_aleaf.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/watts/plugin_aleaf.py b/src/watts/plugin_aleaf.py index f3c706a..69dbea4 100644 --- a/src/watts/plugin_aleaf.py +++ b/src/watts/plugin_aleaf.py @@ -1,7 +1,7 @@ from pathlib import Path import shutil import pandas as pd -from typing import List, Optional +from typing import List, Optional, Dict import os from .plugin import Plugin @@ -33,21 +33,22 @@ def _get_aleaf_csv_data(self) -> pd.DataFrame: class PluginALEAF(Plugin): """Plugin for running ALEAF.""" - def __init__(self, template_file: PathLike, extra_templates: Optional[dict] = None, + def __init__(self, template_file: PathLike, extra_templates: Optional[Dict[str, PathLike]] = None, show_stdout: bool = False, show_stderr: bool = False): - super().__init__([], show_stdout, show_stderr) + super().__init__(extra_inputs=[], show_stdout=show_stdout, show_stderr=show_stderr) self.template_file = template_file self.extra_templates = extra_templates or {} self.plugin_name = 'ALEAF' - self.renderer = TemplateRenderer(self.template_file) + self.renderer = TemplateRenderer(template_file) self.aleaf_dir = os.getenv('ALEAF_DIR') if not self.aleaf_dir: raise EnvironmentError("ALEAF_DIR environment variable is not set.") + self.output_folder = None def prerun(self, params: Parameters) -> None: - """Generate ALEAF input files.""" + """Generate ALEAF input files and check for pre-existing output.""" - # Copy the original ALEAF input file + # Copy the original ALEAF input file to the working directory original_input_path = Path(self.aleaf_dir) / "setting/ALEAF_Master_LC_GTEP.xlsx" modified_input_path = Path("ALEAF_Master_LC_GTEP.xlsx") shutil.copy(original_input_path, modified_input_path) @@ -67,6 +68,28 @@ def prerun(self, params: Parameters) -> None: with pd.ExcelWriter(modified_input_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer: sheet_data.to_excel(writer, sheet_name=sheet_name, index=False) + # Copy the modified input file back to the original directory in ALEAF_DIR + shutil.copy(modified_input_path, original_input_path) + + # Check the 'Simulation Configuration' sheet to find the correct case and CaseID remove the first row + config_sheet = pd.read_excel(modified_input_path, sheet_name='Simulation Configuration', header=1) + # Find the row with Run_Flag set to TRUE + case_row = config_sheet[config_sheet['Run_Flag'] == True].iloc[0] + case_id = case_row['Case_ID'] + # Build the expected output directory and file path based on CaseID + output_folder = Path(self.aleaf_dir) / f"output/LC_GTEP/USA/case_id_{case_row.name+1}_{case_id}" + output_file = output_folder / f"{case_id}__system_tech_summary_EXP.csv" + + # If the output file exists, back it up or delete it before running a new simulation + if output_file.exists(): + backup_folder = output_folder / "backup" + backup_folder.mkdir(parents=True, exist_ok=True) + shutil.move(str(output_file), backup_folder / output_file.name) + + # Save the output folder path for later use in postrun + self.output_folder = output_folder + + def run(self): """Run ALEAF.""" command = ['julia', 'execute_ALEAF.jl'] From e237fb5600ebcd257c108622bc9dad3cfc69bf69 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:57:22 -0500 Subject: [PATCH 04/11] add docstring --- examples/1App_ALAEF_LCGTEP/watts_exec.py | 2 +- src/watts/plugin_aleaf.py | 65 ++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/examples/1App_ALAEF_LCGTEP/watts_exec.py b/examples/1App_ALAEF_LCGTEP/watts_exec.py index 5cd1816..68db9ef 100644 --- a/examples/1App_ALAEF_LCGTEP/watts_exec.py +++ b/examples/1App_ALAEF_LCGTEP/watts_exec.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2023 UChicago Argonne, LLC +# SPDX-FileCopyrightText: 2022-2025 UChicago Argonne, LLC # SPDX-License-Identifier: MIT """ diff --git a/src/watts/plugin_aleaf.py b/src/watts/plugin_aleaf.py index 69dbea4..5583484 100644 --- a/src/watts/plugin_aleaf.py +++ b/src/watts/plugin_aleaf.py @@ -14,7 +14,25 @@ class ResultsALEAF(Results): - """ALEAF simulation results.""" + """ALEAF simulation results. + + Parameters + ---------- + params : Parameters + Parameters used for the simulation. + exec_info : ExecInfo + Execution information. + inputs : List[PathLike] + List of input files used for the simulation. + outputs : List[PathLike] + List of output files generated by the simulation. + + Attributes + ---------- + csv_data : pd.DataFrame + DataFrame containing the results from the ALEAF simulation. + """ + def __init__(self, params: Parameters, exec_info: ExecInfo, inputs: List[PathLike], outputs: List[PathLike]): @@ -31,7 +49,19 @@ def _get_aleaf_csv_data(self) -> pd.DataFrame: class PluginALEAF(Plugin): - """Plugin for running ALEAF.""" + """Plugin for running ALEAF. + + Parameters + ---------- + template_file : PathLike + Path to the template file for ALEAF. + extra_templates : Optional[Dict[str, PathLike]] + Additional templates to be used in the simulation. + show_stdout : bool + Whether to show standard output during execution. + show_stderr : bool + Whether to show standard error during execution. + """ def __init__(self, template_file: PathLike, extra_templates: Optional[Dict[str, PathLike]] = None, show_stdout: bool = False, show_stderr: bool = False): @@ -46,7 +76,13 @@ def __init__(self, template_file: PathLike, extra_templates: Optional[Dict[str, self.output_folder = None def prerun(self, params: Parameters) -> None: - """Generate ALEAF input files and check for pre-existing output.""" + """Generate ALEAF input files and check for pre-existing output. + + Parameters + ---------- + params : Parameters + Parameters to be used for the simulation. + """ # Copy the original ALEAF input file to the working directory original_input_path = Path(self.aleaf_dir) / "setting/ALEAF_Master_LC_GTEP.xlsx" @@ -91,12 +127,31 @@ def prerun(self, params: Parameters) -> None: def run(self): - """Run ALEAF.""" + """Run ALEAF. + + Parameters + ---------- + None + """ + # Ensure the ALEAF directory exists command = ['julia', 'execute_ALEAF.jl'] subprocess.run(command, cwd=self.aleaf_dir) def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsALEAF: - """Collect information from ALEAF simulation and create results object.""" + """Collect information from ALEAF simulation and create results object. + Parameters + ---------- + params : Parameters + Parameters used for the simulation. + exec_info : ExecInfo + exec_info : ExecInfo + Execution information. + + Returns + ------- + ResultsALEAF + Results object containing the simulation results. + """ output_folder = Path(self.aleaf_dir) / f"output/LC_GTEP/USA/case_id_1_Test EXP" outputs = [output_folder / "Test EXP__system_tech_summary_EXP.csv"] return ResultsALEAF(params, exec_info, self.extra_inputs, outputs) From 13bc36149949af4e23b00f2b88abf2f311717773 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:26:33 -0500 Subject: [PATCH 05/11] add plugin usage --- CHANGELOG.md | 2 ++ doc/source/reference/index.rst | 1 + doc/source/user/plugins.rst | 51 ++++++++++++++++++++++++++++ examples/1App_ALAEF_LCGTEP/README.md | 3 +- src/watts/__init__.py | 1 + 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e470274..7258d09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ([#107](https://github.com/watts-dev/watts/pull/107)) * GCMAT plugin via the `PluginGCMAT` class ([#114](https://github.com/watts-dev/watts/pull/114)) +* A-LEAF plugin via the `PluginALEAF` class + ([#119](https://github.com/watts-dev/watts/pull/119)) ### Changes diff --git a/doc/source/reference/index.rst b/doc/source/reference/index.rst index 2c83cbf..cd76c1d 100644 --- a/doc/source/reference/index.rst +++ b/doc/source/reference/index.rst @@ -14,6 +14,7 @@ API Reference watts.PluginGeneric watts.PluginABCE watts.PluginACCERT + watts.PluginALEAF watts.PluginGCMat watts.PluginMCNP watts.PluginMOOSE diff --git a/doc/source/user/plugins.rst b/doc/source/user/plugins.rst index 2e2530a..61eff12 100644 --- a/doc/source/user/plugins.rst +++ b/doc/source/user/plugins.rst @@ -335,6 +335,57 @@ As with other plugins, :class:`~watts.PluginABCE` is easily used by:: .. note:: `ABCE` is still under active development. +A-LEAF Plugin ++++++++++++++ +The :class:`~watts.PluginALEAF` class enables simulations with the Argonne +Low-carbon Electricity Analysis Framework (A-LEAF) code using a text based +input file for fuel price modification. The A-LEAF code using an excel based +input file, and the A-LEAF plugin will only modify the fuel price under the +``Fuel`` section of the input file. Since A-LEAF code is still under +active development, the plugin will be updated as the code is updated. + +The A-LEAF fuel price input file can be templated as follows: + +.. code-block:: jinja + + Scenario FUEL Type Unit 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 + Reference Coal Real 2021 $/MMBtu 1.64 1.61 1.65 1.57 1.52 1.50 1.49 1.50 1.50 1.50 1.50 1.50 1.51 1.52 1.53 1.54 1.56 1.56 1.58 1.59 1.60 1.60 1.62 1.63 1.64 1.65 1.65 1.66 1.66 1.75 1.85 1.96 2.07 2.19 2.31 2.44 2.58 2.73 2.89 + Reference NG Real 2021 $/MMBtu 3.84 3.49 3.17 3.00 2.98 3.08 3.25 3.36 3.46 3.54 3.58 3.65 3.64 3.64 3.65 3.67 3.68 3.69 3.72 3.73 3.70 3.71 3.65 3.61 3.60 3.60 3.62 3.60 3.59 3.80 4.01 4.24 4.48 4.74 5.01 5.29 5.59 5.91 6.25 + Reference Petroleum Real 2021 $/MMBtu 12.18 10.50 11.39 11.61 11.89 12.20 12.46 12.58 12.82 13.08 13.27 13.42 13.54 13.67 13.86 14.03 14.20 14.26 14.55 14.70 14.75 15.00 15.28 15.36 15.58 15.65 15.62 15.65 15.59 16.48 17.42 18.41 19.46 20.57 21.74 22.98 24.29 25.68 27.14 + Reference Nuclear Real 2021 $/MMBtu 0.72 0.72 0.72 0.72 0.72 0.73 0.73 0.73 0.73 0.73 0.73 0.74 0.74 0.74 0.74 0.74 0.74 0.75 0.75 0.75 0.75 0.76 0.76 0.76 0.76 0.76 0.77 0.77 0.77 0.81 0.86 0.91 0.96 1.02 1.07 1.14 1.20 1.27 1.34 + Reference Biomass Real 2020 $/MMBTU 5.00 5.11 5.22 5.34 5.45 5.57 5.70 5.82 5.95 6.08 6.22 6.35 6.49 6.63 6.78 6.93 7.08 7.24 7.40 7.56 7.73 7.90 8.07 8.25 8.43 8.61 8.80 9.00 9.20 9.72 10.27 10.86 11.48 12.13 12.82 13.56 14.33 15.15 16.01 + Reference OGS Real 2020 $/MMBTU 3.84 3.49 3.17 3.00 2.98 3.08 3.25 3.36 3.46 3.54 3.58 3.65 3.64 3.64 3.65 3.67 3.68 3.69 3.72 3.73 3.70 3.71 3.65 3.61 3.60 3.60 3.62 3.60 3.59 3.80 4.01 4.24 4.48 4.74 5.01 5.29 5.59 5.91 6.25 + Base Coal Real 2020 $/MMBTU 1.57 1.54 1.57 1.50 1.45 1.43 1.43 1.43 1.43 1.44 1.43 1.43 1.44 1.45 1.46 1.47 1.49 1.49 1.51 1.52 1.52 1.53 1.54 1.56 1.56 1.57 1.57 1.58 1.58 1.67 1.77 1.87 1.98 2.09 2.21 2.33 2.47 2.61 2.76 + Base NG Real 2020 $/MMBTU 3.67 3.34 3.03 2.87 2.84 2.94 3.10 3.21 3.30 3.38 3.42 3.48 3.48 3.47 3.48 3.50 3.52 3.52 3.55 3.56 3.54 3.54 3.48 3.45 3.44 3.43 3.46 3.44 3.43 3.62 3.83 4.05 4.28 4.52 4.78 5.05 5.34 5.65 5.97 + Base Petroleum Real 2020 $/MMBTU 11.63 10.03 10.88 11.09 11.35 11.65 11.90 12.01 12.24 12.49 12.68 12.82 12.93 13.05 13.24 13.40 13.56 13.61 13.90 14.04 14.09 14.32 14.60 14.67 14.88 14.95 14.92 14.94 14.89 15.74 16.63 17.58 18.58 19.64 20.76 21.95 23.20 24.52 25.92 + Base Nuclear Real 2020 $/MMBTU {{fuel_2022}} {{fuel_2023}} {{fuel_2024}} {{fuel_2025}} {{fuel_2026}} {{fuel_2027}} {{fuel_2028}} {{fuel_2029}} {{fuel_2030}} {{fuel_2031}} {{fuel_2032}} {{fuel_2033}} {{fuel_2034}} {{fuel_2035}} {{fuel_2036}} {{fuel_2037}} {{fuel_2038}} {{fuel_2039}} {{fuel_2040}} {{fuel_2041}} {{fuel_2042}} {{fuel_2043}} {{fuel_2044}} {{fuel_2045}} {{fuel_2046}} {{fuel_2047}} {{fuel_2048}} {{fuel_2049}} {{fuel_2050}} {{fuel_2051}} {{fuel_2052}} {{fuel_2053}} {{fuel_2054}} {{fuel_2055}} {{fuel_2056}} {{fuel_2057}} {{fuel_2058}} {{fuel_2059}} {{fuel_2060}} + Base Biomass Real 2020 $/MMBTU 5.00 5.11 5.22 5.34 5.45 5.57 5.70 5.82 5.95 6.08 6.22 6.35 6.49 6.63 6.78 6.93 7.08 7.24 7.40 7.56 7.73 7.90 8.07 8.25 8.43 8.61 8.80 9.00 9.20 9.72 10.27 10.86 11.48 12.13 12.82 13.56 14.33 15.15 16.01 + Base OGS Real 2020 $/MMBTU 3.67 3.34 3.03 2.87 2.84 2.94 3.10 3.21 3.30 3.38 3.42 3.48 3.48 3.47 3.48 3.50 3.52 3.52 3.55 3.56 3.54 3.54 3.48 3.45 3.44 3.43 3.46 3.44 3.43 3.62 3.83 4.05 4.28 4.52 4.78 5.05 5.34 5.65 5.97 + Low Coal Real 2020 $/MMBTU 1.41 1.38 1.42 1.35 1.31 1.29 1.28 1.29 1.29 1.29 1.29 1.29 1.30 1.30 1.32 1.33 1.34 1.34 1.36 1.37 1.37 1.38 1.39 1.40 1.41 1.42 1.41 1.42 1.43 1.51 1.59 1.68 1.78 1.88 1.99 2.10 2.22 2.35 2.48 + Low NG Real 2020 $/MMBTU 3.30 3.00 2.73 2.58 2.56 2.64 2.79 2.89 2.97 3.05 3.07 3.13 3.13 3.13 3.14 3.15 3.17 3.17 3.20 3.20 3.18 3.19 3.13 3.11 3.10 3.09 3.11 3.09 3.09 3.26 3.45 3.64 3.85 4.07 4.30 4.55 4.81 5.08 5.37 + Low Petroleum Real 2020 $/MMBTU 10.46 9.03 9.79 9.98 10.22 10.48 10.71 10.81 11.02 11.24 11.41 11.53 11.64 11.75 11.91 12.06 12.21 12.25 12.51 12.63 12.68 12.89 13.14 13.21 13.39 13.45 13.43 13.45 13.40 14.16 14.97 15.82 16.73 17.68 18.69 19.75 20.88 22.07 23.33 + Low Nuclear Real 2020 $/MMBTU 0.62 0.62 0.62 0.62 0.62 0.62 0.62 0.63 0.63 0.63 0.63 0.63 0.63 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 1.03 1.09 1.15 + Low Biomass Real 2020 $/MMBTU 4.50 4.60 4.70 4.80 4.91 5.02 5.13 5.24 5.36 5.47 5.59 5.72 5.84 5.97 6.10 6.24 6.37 6.51 6.66 6.80 6.95 7.11 7.26 7.42 7.59 7.75 7.92 8.10 8.28 8.75 9.25 9.77 10.33 10.92 11.54 12.20 12.90 13.63 14.41 + Low OGS Real 2020 $/MMBTU 3.30 3.00 2.73 2.58 2.56 2.64 2.79 2.89 2.97 3.05 3.07 3.13 3.13 3.13 3.14 3.15 3.17 3.17 3.20 3.20 3.18 3.19 3.13 3.11 3.10 3.09 3.11 3.09 3.09 3.26 3.45 3.64 3.85 4.07 4.30 4.55 4.81 5.08 5.37 + +Before running the A-LEAF plugin, you need to specify the directory that the +executable and the license key are in (they must be in the same directory). This +can be done by adding the ``A-LEAF_DIR`` variable to the environment or by +explicitly specifying the path in the Python script as:: + + aleaf_plugin = watts.PluginALEAF( + 'aleaf_template', + executable="/path/to/A-LEAF" + ) + +The A-LEAF plugin can be instantiated with the following command line:: + + aleaf_plugin = watts.PluginALEAF('aleaf_template') + +As with other plugins, :class:`~watts.PluginALEAF` can be used by calling the +:meth:`~watts.PluginALEAF` instance directly the same way as other plugins. + aleaf_plugin = watts.PluginALEAF('aleaf_template') + aleaf_result = aleaf_plugin(params) Dakota Plugin +++++++++++++ diff --git a/examples/1App_ALAEF_LCGTEP/README.md b/examples/1App_ALAEF_LCGTEP/README.md index 656a1e9..8dd1e0c 100644 --- a/examples/1App_ALAEF_LCGTEP/README.md +++ b/examples/1App_ALAEF_LCGTEP/README.md @@ -13,9 +13,10 @@ This example provides a demonstration for using WATTS to run and analyze simulat ## Keywords - Energy Market +- Capacity Expansion - Economic Modeling ## File descriptions - [__watts_exec.py__](watts_exec.py): WATTS workflow for this example. This is the file to execute to run the problem described above. -- [__gcmat_template__](Fuel.txt): Fuel price for this example +- [__A-LEAF_fuel_template__](Fuel.txt): Fuel price for this example diff --git a/src/watts/__init__.py b/src/watts/__init__.py index d4683d9..970b885 100644 --- a/src/watts/__init__.py +++ b/src/watts/__init__.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT from .plugin import * +from .plugin_aleaf import * from .plugin_accert import * from .plugin_openmc import * from .plugin_moose import * From e542382bdf7d27848ccda8d660f55c0113af9acb Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:35:54 -0500 Subject: [PATCH 06/11] remove init typo --- src/watts/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/watts/__init__.py b/src/watts/__init__.py index 970b885..199d14e 100644 --- a/src/watts/__init__.py +++ b/src/watts/__init__.py @@ -14,7 +14,6 @@ from .plugin_abce import * from .plugin_dakota import * from .plugin_gcmat import * -from .plugin_aleaf import * from .results import * from .template import * from .parameters import * From 557cbe4241f164028e2eb55bd870d99a6041fca7 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:42:36 -0500 Subject: [PATCH 07/11] address comments --- doc/source/user/plugins.rst | 36 ++++++++-------------- examples/1App_ALAEF_LCGTEP/Fuel.txt | 2 +- examples/1App_ALAEF_LCGTEP/watts_exec.py | 39 +++++++++++++++--------- src/watts/plugin_aleaf.py | 3 +- 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/doc/source/user/plugins.rst b/doc/source/user/plugins.rst index 61eff12..f1c8a9e 100644 --- a/doc/source/user/plugins.rst +++ b/doc/source/user/plugins.rst @@ -348,25 +348,15 @@ The A-LEAF fuel price input file can be templated as follows: .. code-block:: jinja - Scenario FUEL Type Unit 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 - Reference Coal Real 2021 $/MMBtu 1.64 1.61 1.65 1.57 1.52 1.50 1.49 1.50 1.50 1.50 1.50 1.50 1.51 1.52 1.53 1.54 1.56 1.56 1.58 1.59 1.60 1.60 1.62 1.63 1.64 1.65 1.65 1.66 1.66 1.75 1.85 1.96 2.07 2.19 2.31 2.44 2.58 2.73 2.89 - Reference NG Real 2021 $/MMBtu 3.84 3.49 3.17 3.00 2.98 3.08 3.25 3.36 3.46 3.54 3.58 3.65 3.64 3.64 3.65 3.67 3.68 3.69 3.72 3.73 3.70 3.71 3.65 3.61 3.60 3.60 3.62 3.60 3.59 3.80 4.01 4.24 4.48 4.74 5.01 5.29 5.59 5.91 6.25 - Reference Petroleum Real 2021 $/MMBtu 12.18 10.50 11.39 11.61 11.89 12.20 12.46 12.58 12.82 13.08 13.27 13.42 13.54 13.67 13.86 14.03 14.20 14.26 14.55 14.70 14.75 15.00 15.28 15.36 15.58 15.65 15.62 15.65 15.59 16.48 17.42 18.41 19.46 20.57 21.74 22.98 24.29 25.68 27.14 - Reference Nuclear Real 2021 $/MMBtu 0.72 0.72 0.72 0.72 0.72 0.73 0.73 0.73 0.73 0.73 0.73 0.74 0.74 0.74 0.74 0.74 0.74 0.75 0.75 0.75 0.75 0.76 0.76 0.76 0.76 0.76 0.77 0.77 0.77 0.81 0.86 0.91 0.96 1.02 1.07 1.14 1.20 1.27 1.34 - Reference Biomass Real 2020 $/MMBTU 5.00 5.11 5.22 5.34 5.45 5.57 5.70 5.82 5.95 6.08 6.22 6.35 6.49 6.63 6.78 6.93 7.08 7.24 7.40 7.56 7.73 7.90 8.07 8.25 8.43 8.61 8.80 9.00 9.20 9.72 10.27 10.86 11.48 12.13 12.82 13.56 14.33 15.15 16.01 - Reference OGS Real 2020 $/MMBTU 3.84 3.49 3.17 3.00 2.98 3.08 3.25 3.36 3.46 3.54 3.58 3.65 3.64 3.64 3.65 3.67 3.68 3.69 3.72 3.73 3.70 3.71 3.65 3.61 3.60 3.60 3.62 3.60 3.59 3.80 4.01 4.24 4.48 4.74 5.01 5.29 5.59 5.91 6.25 - Base Coal Real 2020 $/MMBTU 1.57 1.54 1.57 1.50 1.45 1.43 1.43 1.43 1.43 1.44 1.43 1.43 1.44 1.45 1.46 1.47 1.49 1.49 1.51 1.52 1.52 1.53 1.54 1.56 1.56 1.57 1.57 1.58 1.58 1.67 1.77 1.87 1.98 2.09 2.21 2.33 2.47 2.61 2.76 - Base NG Real 2020 $/MMBTU 3.67 3.34 3.03 2.87 2.84 2.94 3.10 3.21 3.30 3.38 3.42 3.48 3.48 3.47 3.48 3.50 3.52 3.52 3.55 3.56 3.54 3.54 3.48 3.45 3.44 3.43 3.46 3.44 3.43 3.62 3.83 4.05 4.28 4.52 4.78 5.05 5.34 5.65 5.97 - Base Petroleum Real 2020 $/MMBTU 11.63 10.03 10.88 11.09 11.35 11.65 11.90 12.01 12.24 12.49 12.68 12.82 12.93 13.05 13.24 13.40 13.56 13.61 13.90 14.04 14.09 14.32 14.60 14.67 14.88 14.95 14.92 14.94 14.89 15.74 16.63 17.58 18.58 19.64 20.76 21.95 23.20 24.52 25.92 - Base Nuclear Real 2020 $/MMBTU {{fuel_2022}} {{fuel_2023}} {{fuel_2024}} {{fuel_2025}} {{fuel_2026}} {{fuel_2027}} {{fuel_2028}} {{fuel_2029}} {{fuel_2030}} {{fuel_2031}} {{fuel_2032}} {{fuel_2033}} {{fuel_2034}} {{fuel_2035}} {{fuel_2036}} {{fuel_2037}} {{fuel_2038}} {{fuel_2039}} {{fuel_2040}} {{fuel_2041}} {{fuel_2042}} {{fuel_2043}} {{fuel_2044}} {{fuel_2045}} {{fuel_2046}} {{fuel_2047}} {{fuel_2048}} {{fuel_2049}} {{fuel_2050}} {{fuel_2051}} {{fuel_2052}} {{fuel_2053}} {{fuel_2054}} {{fuel_2055}} {{fuel_2056}} {{fuel_2057}} {{fuel_2058}} {{fuel_2059}} {{fuel_2060}} - Base Biomass Real 2020 $/MMBTU 5.00 5.11 5.22 5.34 5.45 5.57 5.70 5.82 5.95 6.08 6.22 6.35 6.49 6.63 6.78 6.93 7.08 7.24 7.40 7.56 7.73 7.90 8.07 8.25 8.43 8.61 8.80 9.00 9.20 9.72 10.27 10.86 11.48 12.13 12.82 13.56 14.33 15.15 16.01 - Base OGS Real 2020 $/MMBTU 3.67 3.34 3.03 2.87 2.84 2.94 3.10 3.21 3.30 3.38 3.42 3.48 3.48 3.47 3.48 3.50 3.52 3.52 3.55 3.56 3.54 3.54 3.48 3.45 3.44 3.43 3.46 3.44 3.43 3.62 3.83 4.05 4.28 4.52 4.78 5.05 5.34 5.65 5.97 - Low Coal Real 2020 $/MMBTU 1.41 1.38 1.42 1.35 1.31 1.29 1.28 1.29 1.29 1.29 1.29 1.29 1.30 1.30 1.32 1.33 1.34 1.34 1.36 1.37 1.37 1.38 1.39 1.40 1.41 1.42 1.41 1.42 1.43 1.51 1.59 1.68 1.78 1.88 1.99 2.10 2.22 2.35 2.48 - Low NG Real 2020 $/MMBTU 3.30 3.00 2.73 2.58 2.56 2.64 2.79 2.89 2.97 3.05 3.07 3.13 3.13 3.13 3.14 3.15 3.17 3.17 3.20 3.20 3.18 3.19 3.13 3.11 3.10 3.09 3.11 3.09 3.09 3.26 3.45 3.64 3.85 4.07 4.30 4.55 4.81 5.08 5.37 - Low Petroleum Real 2020 $/MMBTU 10.46 9.03 9.79 9.98 10.22 10.48 10.71 10.81 11.02 11.24 11.41 11.53 11.64 11.75 11.91 12.06 12.21 12.25 12.51 12.63 12.68 12.89 13.14 13.21 13.39 13.45 13.43 13.45 13.40 14.16 14.97 15.82 16.73 17.68 18.69 19.75 20.88 22.07 23.33 - Low Nuclear Real 2020 $/MMBTU 0.62 0.62 0.62 0.62 0.62 0.62 0.62 0.63 0.63 0.63 0.63 0.63 0.63 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 0.41 1.03 1.09 1.15 - Low Biomass Real 2020 $/MMBTU 4.50 4.60 4.70 4.80 4.91 5.02 5.13 5.24 5.36 5.47 5.59 5.72 5.84 5.97 6.10 6.24 6.37 6.51 6.66 6.80 6.95 7.11 7.26 7.42 7.59 7.75 7.92 8.10 8.28 8.75 9.25 9.77 10.33 10.92 11.54 12.20 12.90 13.63 14.41 - Low OGS Real 2020 $/MMBTU 3.30 3.00 2.73 2.58 2.56 2.64 2.79 2.89 2.97 3.05 3.07 3.13 3.13 3.13 3.14 3.15 3.17 3.17 3.20 3.20 3.18 3.19 3.13 3.11 3.10 3.09 3.11 3.09 3.09 3.26 3.45 3.64 3.85 4.07 4.30 4.55 4.81 5.08 5.37 + Scenario FUEL Type Unit 2022 2023 2024 2025 2026 ... + + Reference Coal Real 2022 $/MMBtu 1.64 1.61 1.65 1.57 1.52 ... + ... + Base Nuclear Real 2020 $/MMBTU {% for year, price in fuel_price.items() %} + {{ price }}{% if not loop.last %} {% endif %}{% endfor %} + Base Biomass Real 2020 $/MMBTU 5.00 5.11 5.22 5.34 5.45 ... + ... + Low OGS Real 2020 $/MMBTU 3.30 3.00 2.73 2.58 2.56 ... Before running the A-LEAF plugin, you need to specify the directory that the executable and the license key are in (they must be in the same directory). This @@ -375,18 +365,16 @@ explicitly specifying the path in the Python script as:: aleaf_plugin = watts.PluginALEAF( 'aleaf_template', - executable="/path/to/A-LEAF" + executable="/path/to/A-LEAF", + extra_inputs=[] ) -The A-LEAF plugin can be instantiated with the following command line:: - - aleaf_plugin = watts.PluginALEAF('aleaf_template') - As with other plugins, :class:`~watts.PluginALEAF` can be used by calling the :meth:`~watts.PluginALEAF` instance directly the same way as other plugins. aleaf_plugin = watts.PluginALEAF('aleaf_template') aleaf_result = aleaf_plugin(params) + Dakota Plugin +++++++++++++ diff --git a/examples/1App_ALAEF_LCGTEP/Fuel.txt b/examples/1App_ALAEF_LCGTEP/Fuel.txt index 66d190c..42af8fd 100644 --- a/examples/1App_ALAEF_LCGTEP/Fuel.txt +++ b/examples/1App_ALAEF_LCGTEP/Fuel.txt @@ -8,7 +8,7 @@ Reference OGS Real 2020 $/MMBTU 3.84 3.49 3.17 3.00 2.98 3.08 3.25 3.36 3.46 3.5 Base Coal Real 2020 $/MMBTU 1.57 1.54 1.57 1.50 1.45 1.43 1.43 1.43 1.43 1.44 1.43 1.43 1.44 1.45 1.46 1.47 1.49 1.49 1.51 1.52 1.52 1.53 1.54 1.56 1.56 1.57 1.57 1.58 1.58 1.67 1.77 1.87 1.98 2.09 2.21 2.33 2.47 2.61 2.76 Base NG Real 2020 $/MMBTU 3.67 3.34 3.03 2.87 2.84 2.94 3.10 3.21 3.30 3.38 3.42 3.48 3.48 3.47 3.48 3.50 3.52 3.52 3.55 3.56 3.54 3.54 3.48 3.45 3.44 3.43 3.46 3.44 3.43 3.62 3.83 4.05 4.28 4.52 4.78 5.05 5.34 5.65 5.97 Base Petroleum Real 2020 $/MMBTU 11.63 10.03 10.88 11.09 11.35 11.65 11.90 12.01 12.24 12.49 12.68 12.82 12.93 13.05 13.24 13.40 13.56 13.61 13.90 14.04 14.09 14.32 14.60 14.67 14.88 14.95 14.92 14.94 14.89 15.74 16.63 17.58 18.58 19.64 20.76 21.95 23.20 24.52 25.92 -Base Nuclear Real 2020 $/MMBTU {{fuel_2022}} {{fuel_2023}} {{fuel_2024}} {{fuel_2025}} {{fuel_2026}} {{fuel_2027}} {{fuel_2028}} {{fuel_2029}} {{fuel_2030}} {{fuel_2031}} {{fuel_2032}} {{fuel_2033}} {{fuel_2034}} {{fuel_2035}} {{fuel_2036}} {{fuel_2037}} {{fuel_2038}} {{fuel_2039}} {{fuel_2040}} {{fuel_2041}} {{fuel_2042}} {{fuel_2043}} {{fuel_2044}} {{fuel_2045}} {{fuel_2046}} {{fuel_2047}} {{fuel_2048}} {{fuel_2049}} {{fuel_2050}} {{fuel_2051}} {{fuel_2052}} {{fuel_2053}} {{fuel_2054}} {{fuel_2055}} {{fuel_2056}} {{fuel_2057}} {{fuel_2058}} {{fuel_2059}} {{fuel_2060}} +Base Nuclear Real 2020 $/MMBTU {% for year, price in fuel_price.items() %}{{ price }}{% if not loop.last %} {% endif %}{% endfor %} Base Biomass Real 2020 $/MMBTU 5.00 5.11 5.22 5.34 5.45 5.57 5.70 5.82 5.95 6.08 6.22 6.35 6.49 6.63 6.78 6.93 7.08 7.24 7.40 7.56 7.73 7.90 8.07 8.25 8.43 8.61 8.80 9.00 9.20 9.72 10.27 10.86 11.48 12.13 12.82 13.56 14.33 15.15 16.01 Base OGS Real 2020 $/MMBTU 3.67 3.34 3.03 2.87 2.84 2.94 3.10 3.21 3.30 3.38 3.42 3.48 3.48 3.47 3.48 3.50 3.52 3.52 3.55 3.56 3.54 3.54 3.48 3.45 3.44 3.43 3.46 3.44 3.43 3.62 3.83 4.05 4.28 4.52 4.78 5.05 5.34 5.65 5.97 Low Coal Real 2020 $/MMBTU 1.41 1.38 1.42 1.35 1.31 1.29 1.28 1.29 1.29 1.29 1.29 1.29 1.30 1.30 1.32 1.33 1.34 1.34 1.36 1.37 1.37 1.38 1.39 1.40 1.41 1.42 1.41 1.42 1.43 1.51 1.59 1.68 1.78 1.88 1.99 2.10 2.22 2.35 2.48 diff --git a/examples/1App_ALAEF_LCGTEP/watts_exec.py b/examples/1App_ALAEF_LCGTEP/watts_exec.py index 68db9ef..cc081e2 100644 --- a/examples/1App_ALAEF_LCGTEP/watts_exec.py +++ b/examples/1App_ALAEF_LCGTEP/watts_exec.py @@ -7,13 +7,12 @@ import watts from pathlib import Path -import numpy as np -import time +import pandas as pd params = watts.Parameters() -reference_prices = { +fuel_price = { 2022: 0.62, 2023: 0.62, 2024: 0.62, 2025: 0.62, 2026: 0.62, 2027: 0.62, 2028: 0.63, 2029: 0.63, 2030: 0.63, 2031: 0.76, 2032: 0.76, 2033: 0.76, 2034: 0.76, 2035: 0.76, 2036: 0.77, 2037: 0.77, 2038: 0.77, 2039: 0.77, @@ -27,19 +26,20 @@ # Set nuclear prices from reference and calculated growth starting_year = 2031 starting_price = 0.76 -# Set nuclear prices from reference up to 2030 -for year in range(2022, starting_year): - params[f'fuel_{year}'] = reference_prices[year] -# Assign the starting price for 2031 -params[f'fuel_{starting_year}'] = starting_price - -# Compute prices from 2031 onwards using the growth rates +# Compute prices dynamically for years after the starting year +growth_rates = {year: 1.0028 if year <= 2050 else 1.057 for year in range(starting_year + 1, 2061)} +computed_prices = {starting_year: starting_price} for year in range(starting_year + 1, 2061): - prev_year_price = params[f'fuel_{year - 1}'] - growth_factor = 1.0028 if year <= 2050 else 1.057 - params[f'fuel_{year}'] = round(prev_year_price * growth_factor, 3) + prev_price = computed_prices[year - 1] + computed_prices[year] = round(prev_price * growth_rates[year], 3) + +fuel_price.update(computed_prices) +# Initialize parameters and pass the fuel price dictionary +params = watts.Parameters() +params['fuel_price'] = fuel_price +# Display parameter summary params.show_summary(show_metadata=True, sort_by='key') # Set default path for results @@ -54,5 +54,14 @@ aleaf_result = aleaf_plugin(params) print('ALEAF simulation completed.') -# Collect and display results -print(aleaf_result.csv_data) +# Get the technology summary +techsummary = aleaf_result.csv_data +# keep only the capacity columns +techsummary = techsummary[['Year', 'UnitGroup', 'Unit_Type', 'Fuel', 'ICAP', 'ICap_New', 'ICap_Ret']] + +# group the technology summary by year and fuel +grouped_df = techsummary.groupby(['Year','Fuel']).sum() +# print out the grouped dataframe +print(grouped_df) + + diff --git a/src/watts/plugin_aleaf.py b/src/watts/plugin_aleaf.py index 5583484..302a5f8 100644 --- a/src/watts/plugin_aleaf.py +++ b/src/watts/plugin_aleaf.py @@ -90,7 +90,8 @@ def prerun(self, params: Parameters) -> None: shutil.copy(original_input_path, modified_input_path) # Load the Excel file and modify the 'Fuel' sheet - fuel_data = self.renderer(params) + self.renderer(params = params, filename = self.template_file) + fuel_data = pd.read_csv("Fuel.txt", sep="\t") with pd.ExcelWriter(modified_input_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer: fuel_sheet = pd.read_excel(writer, sheet_name='Fuel') From 5e8ba3edeb1c0860de0bebfa6bd23b7ff8ae6fb2 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:55:03 -0500 Subject: [PATCH 08/11] address comments from paul --- src/watts/plugin_aleaf.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/watts/plugin_aleaf.py b/src/watts/plugin_aleaf.py index 302a5f8..4e1bdb0 100644 --- a/src/watts/plugin_aleaf.py +++ b/src/watts/plugin_aleaf.py @@ -4,7 +4,7 @@ from typing import List, Optional, Dict import os -from .plugin import Plugin +from .plugin import Plugin, _find_executable from .results import Results, ExecInfo from .fileutils import PathLike from .parameters import Parameters @@ -63,9 +63,12 @@ class PluginALEAF(Plugin): Whether to show standard error during execution. """ - def __init__(self, template_file: PathLike, extra_templates: Optional[Dict[str, PathLike]] = None, + def __init__(self, template_file: PathLike, + executable: PathLike = 'execute_ALEAF.jl', + extra_templates: Optional[Dict[str, PathLike]] = None, show_stdout: bool = False, show_stderr: bool = False): - super().__init__(extra_inputs=[], show_stdout=show_stdout, show_stderr=show_stderr) + executable =_find_executable(executable, 'ALEAF_DIR') + super().__init__(executable, extra_inputs=[], show_stdout=show_stdout, show_stderr=show_stderr) self.template_file = template_file self.extra_templates = extra_templates or {} self.plugin_name = 'ALEAF' @@ -75,6 +78,16 @@ def __init__(self, template_file: PathLike, extra_templates: Optional[Dict[str, raise EnvironmentError("ALEAF_DIR environment variable is not set.") self.output_folder = None + @PluginGeneric.executable.setter + def executable(self, exe: PathLike): + if not exe.is_file(): + raise RuntimeError( + f"{self.plugin_name} module '{exe}' does not exist. The " + "ALEAF_DIR environment variable needs to be set to a directory " + "containing the execute_ALEAF.jl module." + ) + self._executable = Path(exe) + def prerun(self, params: Parameters) -> None: """Generate ALEAF input files and check for pre-existing output. From 03bea7e9e60b6e07ce2d25dc09f0648666ae2cc8 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 30 Apr 2025 15:39:31 -0500 Subject: [PATCH 09/11] add plugin generic --- src/watts/plugin_aleaf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/watts/plugin_aleaf.py b/src/watts/plugin_aleaf.py index 4e1bdb0..eb170e1 100644 --- a/src/watts/plugin_aleaf.py +++ b/src/watts/plugin_aleaf.py @@ -4,7 +4,7 @@ from typing import List, Optional, Dict import os -from .plugin import Plugin, _find_executable +from .plugin import Plugin, PluginGeneric, _find_executable from .results import Results, ExecInfo from .fileutils import PathLike from .parameters import Parameters From cf586f1bf745b9a636e5224c1136d4ad0352e01d Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 30 Apr 2025 15:41:01 -0500 Subject: [PATCH 10/11] remove juia dep --- examples/1App_ALAEF_LCGTEP/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/1App_ALAEF_LCGTEP/README.md b/examples/1App_ALAEF_LCGTEP/README.md index 8dd1e0c..b9c8f05 100644 --- a/examples/1App_ALAEF_LCGTEP/README.md +++ b/examples/1App_ALAEF_LCGTEP/README.md @@ -8,7 +8,6 @@ This example provides a demonstration for using WATTS to run and analyze simulat ## Code(s) - A-LEAF -- Julia (A-LEAF dependency) ## Keywords From f1b8178c6989984d7a15232a30b0abbaebfee083 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 5 May 2025 18:43:16 -0500 Subject: [PATCH 11/11] Small edits from review --- doc/source/conf.py | 17 ++++--------- doc/source/reference/index.rst | 6 ++++- doc/source/user/plugins.rst | 27 ++++++++++---------- pyproject.toml | 1 + src/watts/plugin_aleaf.py | 45 +++++++++++++++------------------- 5 files changed, 45 insertions(+), 51 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 7376e20..0755865 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -78,18 +78,6 @@ html_theme_options = { "github_url": "https://github.com/watts-dev/watts", - "favicons": [ - { - "rel": "icon", - "sizes": "16x16", - "href": "watts_16x16.png", - }, - { - "rel": "icon", - "sizes": "32x32", - "href": "watts_32x32.png", - }, - ], "switcher": { "json_url": "https://watts.readthedocs.io/en/latest/_static/switcher.json", "version_match": version if '-dev' not in version else 'dev', @@ -97,3 +85,8 @@ "navbar_end": ["version-switcher", "navbar-icon-links"], "show_toc_level": 3, } + +favicons = [ + "watts_16x16.png", + "watts_32x32.png", +] diff --git a/doc/source/reference/index.rst b/doc/source/reference/index.rst index cd76c1d..7268bcd 100644 --- a/doc/source/reference/index.rst +++ b/doc/source/reference/index.rst @@ -15,7 +15,7 @@ API Reference watts.PluginABCE watts.PluginACCERT watts.PluginALEAF - watts.PluginGCMat + watts.PluginGCMAT watts.PluginMCNP watts.PluginMOOSE watts.PluginOpenMC @@ -24,6 +24,10 @@ API Reference watts.PluginSAS watts.PluginSerpent watts.Results + watts.ResultsABCE + watts.ResultsACCERT + watts.ResultsALEAF + watts.ResultsGCMAT watts.ResultsMCNP watts.ResultsMOOSE watts.ResultsOpenMC diff --git a/doc/source/user/plugins.rst b/doc/source/user/plugins.rst index f1c8a9e..7853cb0 100644 --- a/doc/source/user/plugins.rst +++ b/doc/source/user/plugins.rst @@ -337,12 +337,12 @@ As with other plugins, :class:`~watts.PluginABCE` is easily used by:: A-LEAF Plugin +++++++++++++ -The :class:`~watts.PluginALEAF` class enables simulations with the Argonne -Low-carbon Electricity Analysis Framework (A-LEAF) code using a text based -input file for fuel price modification. The A-LEAF code using an excel based -input file, and the A-LEAF plugin will only modify the fuel price under the -``Fuel`` section of the input file. Since A-LEAF code is still under -active development, the plugin will be updated as the code is updated. +The :class:`~watts.PluginALEAF` class enables simulations with the Argonne +Low-carbon Electricity Analysis Framework (A-LEAF) code using a text-based input +file for fuel price modification. The A-LEAF code uses an Excel-based input +file, and the A-LEAF plugin will only modify the fuel price under the ``Fuel`` +section of the input file. Since the A-LEAF code is still under active +development, the plugin will be updated as the code is updated. The A-LEAF fuel price input file can be templated as follows: @@ -357,10 +357,10 @@ The A-LEAF fuel price input file can be templated as follows: Base Biomass Real 2020 $/MMBTU 5.00 5.11 5.22 5.34 5.45 ... ... Low OGS Real 2020 $/MMBTU 3.30 3.00 2.73 2.58 2.56 ... - + Before running the A-LEAF plugin, you need to specify the directory that the executable and the license key are in (they must be in the same directory). This -can be done by adding the ``A-LEAF_DIR`` variable to the environment or by +can be done by adding the ``ALEAF_DIR`` variable to the environment or by explicitly specifying the path in the Python script as:: aleaf_plugin = watts.PluginALEAF( @@ -370,7 +370,8 @@ explicitly specifying the path in the Python script as:: ) As with other plugins, :class:`~watts.PluginALEAF` can be used by calling the -:meth:`~watts.PluginALEAF` instance directly the same way as other plugins. +:meth:`~watts.PluginALEAF` instance directly the same way as other plugins:: + aleaf_plugin = watts.PluginALEAF('aleaf_template') aleaf_result = aleaf_plugin(params) @@ -515,7 +516,7 @@ As with other plugins, :class:`~watts.PluginACCERT` is used by:: GCMat Plugin ++++++++++++ -The :class:`~watts.PluginGCMat` class enables simulations with Argonne's global +The :class:`~watts.PluginGCMAT` class enables simulations with Argonne's global critical materials agent-based model (GCMat). This code simulates dynamic economic markets that are composed of agents who have complex decision-making behaviors, and interact with and influence each other, possibly indirectly @@ -539,7 +540,7 @@ The GCMat plugin requires a template input file that can be templated as follows The GCMat plugin can be instantiated with the following command line:: - gcmat_plugin = watts.PluginGCMat('gcmat_template') + gcmat_plugin = watts.PluginGCMAT('gcmat_template') Before running the GCMat plugin, the directory that contains the executable 'run_repast.sh' must be set. This can be done by setting the ``GCMAT_DIR`` @@ -547,7 +548,7 @@ environment variable:: export GCMAT_DIR='/path/to/gcmat/output' -As with other plugins, :class:`~watts.PluginGCMat` is used by:: +As with other plugins, :class:`~watts.PluginGCMAT` is used by:: - gcmat_plugin = watts.PluginGCMat('gcmat_template') + gcmat_plugin = watts.PluginGCMAT('gcmat_template') gcmat_result = gcmat_plugin(params) diff --git a/pyproject.toml b/pyproject.toml index d947083..b5f97e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ docs = [ "pydata_sphinx_theme", "sphinx_design", "sphinx-autodoc-typehints", + "sphinx-favicon", ] [project.urls] diff --git a/src/watts/plugin_aleaf.py b/src/watts/plugin_aleaf.py index eb170e1..21e7cf7 100644 --- a/src/watts/plugin_aleaf.py +++ b/src/watts/plugin_aleaf.py @@ -18,18 +18,18 @@ class ResultsALEAF(Results): Parameters ---------- - params : Parameters + params Parameters used for the simulation. - exec_info : ExecInfo + exec_info Execution information. - inputs : List[PathLike] + inputs List of input files used for the simulation. - outputs : List[PathLike] + outputs List of output files generated by the simulation. - + Attributes ---------- - csv_data : pd.DataFrame + csv_data DataFrame containing the results from the ALEAF simulation. """ @@ -53,17 +53,19 @@ class PluginALEAF(Plugin): Parameters ---------- - template_file : PathLike + template_file Path to the template file for ALEAF. - extra_templates : Optional[Dict[str, PathLike]] + executable + Path to ALEAF executable + extra_templates Additional templates to be used in the simulation. - show_stdout : bool + show_stdout Whether to show standard output during execution. - show_stderr : bool + show_stderr Whether to show standard error during execution. """ - def __init__(self, template_file: PathLike, + def __init__(self, template_file: PathLike, executable: PathLike = 'execute_ALEAF.jl', extra_templates: Optional[Dict[str, PathLike]] = None, show_stdout: bool = False, show_stderr: bool = False): @@ -93,7 +95,7 @@ def prerun(self, params: Parameters) -> None: Parameters ---------- - params : Parameters + params Parameters to be used for the simulation. """ @@ -124,7 +126,7 @@ def prerun(self, params: Parameters) -> None: # Check the 'Simulation Configuration' sheet to find the correct case and CaseID remove the first row config_sheet = pd.read_excel(modified_input_path, sheet_name='Simulation Configuration', header=1) # Find the row with Run_Flag set to TRUE - case_row = config_sheet[config_sheet['Run_Flag'] == True].iloc[0] + case_row = config_sheet[config_sheet['Run_Flag'] is True].iloc[0] case_id = case_row['Case_ID'] # Build the expected output directory and file path based on CaseID output_folder = Path(self.aleaf_dir) / f"output/LC_GTEP/USA/case_id_{case_row.name+1}_{case_id}" @@ -139,14 +141,8 @@ def prerun(self, params: Parameters) -> None: # Save the output folder path for later use in postrun self.output_folder = output_folder - def run(self): - """Run ALEAF. - - Parameters - ---------- - None - """ + """Run ALEAF.""" # Ensure the ALEAF directory exists command = ['julia', 'execute_ALEAF.jl'] subprocess.run(command, cwd=self.aleaf_dir) @@ -155,17 +151,16 @@ def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsALEAF: """Collect information from ALEAF simulation and create results object. Parameters ---------- - params : Parameters + params Parameters used for the simulation. - exec_info : ExecInfo - exec_info : ExecInfo - Execution information. + exec_info : ExecInfo + Execution information. Returns ------- ResultsALEAF Results object containing the simulation results. """ - output_folder = Path(self.aleaf_dir) / f"output/LC_GTEP/USA/case_id_1_Test EXP" + output_folder = Path(self.aleaf_dir) / "output/LC_GTEP/USA/case_id_1_Test EXP" outputs = [output_folder / "Test EXP__system_tech_summary_EXP.csv"] return ResultsALEAF(params, exec_info, self.extra_inputs, outputs)