From 631e412e21fa4606fade642797198ea5b9223748 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Tue, 13 Aug 2024 23:59:02 -0500 Subject: [PATCH 01/15] add test --- examples/1App_GCMat/README.md | 23 ++++ examples/1App_GCMat/gcmat_template.txt | 6 + examples/1App_GCMat/watts_exec.py | 90 +++++++++++++++ src/watts/__init__.py | 1 + src/watts/plugin_gcmat.py | 147 +++++++++++++++++++++++++ 5 files changed, 267 insertions(+) create mode 100644 examples/1App_GCMat/README.md create mode 100644 examples/1App_GCMat/gcmat_template.txt create mode 100644 examples/1App_GCMat/watts_exec.py create mode 100644 src/watts/plugin_gcmat.py diff --git a/examples/1App_GCMat/README.md b/examples/1App_GCMat/README.md new file mode 100644 index 00000000..26dab0f4 --- /dev/null +++ b/examples/1App_GCMat/README.md @@ -0,0 +1,23 @@ +# 1App_ABCE_C2N + +## Purpose + +This example provides a demonstration for using WATTS to perform an +agent based capacity expansion with ABCE for a coal-to-nuclear (C2N) +transition scenario. + +## Code(s) + +- ABCE +- A-LEAF (ABCE dependency) + +## Keywords + +- Capacity Expansion +- Agent Based Modeling +- Electricity Markets + +## 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. +- [__abce_template__](abce_template.txt): Templated ABCE model. diff --git a/examples/1App_GCMat/gcmat_template.txt b/examples/1App_GCMat/gcmat_template.txt new file mode 100644 index 00000000..560ff4fc --- /dev/null +++ b/examples/1App_GCMat/gcmat_template.txt @@ -0,0 +1,6 @@ +region final demand agent final demand product reference product unit 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 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 min max + final demand U U U tonnes 111847.841748839 112977.61792812 114118.805988 115271.5212 116435.88 117612 118800 120000 121200 122412 123636.12 124872.4812 126121.206012 127382.41807212 128656.242252841 {{final_demand_2025}} 131242.232722123 132554.655049345 133880.201599838 135219.003615836 136571.193651995 137936.905588515 139316.2746444 140709.437390844 142116.531764752 143537.6970824 144973.074053224 146422.804793756 147887.032841694 149365.903170111 150859.562201812 152368.15782383 153891.839402068 155430.757796089 156985.06537405 158554.91602779 160140.465188068 161741.869839949 163359.288538348 164992.881423732 166642.810237969 +China final demand U U shares of total 0.107142857142857 0.106698999696878 0.112674964564139 0.114434523188336 0.116194081812533 0.1278 0.1299 0.132 0.1341 0.1362 0.1383 0.1404 0.1425 0.1446 0.1467 {{china_2025}} 0.1509 0.153 0.1551 0.1572 0.1593 0.107142857142857 0.106698999696878 0.112674964564139 0.114434523188336 0.116194081812533 0.1278 0.1299 0.132 0.1341 0.1362 0.1383 0.1404 0.1425 0.1446 0.1467 0.1488 0.1509 0.153 0.1551 0.1572 +US final demand U U shares of total 0.206589879692216 0.201409879668034 0.199574650237538 0.196450274218913 0.194620873740305 0.193447312012611 0.190358597294858 0.187635077997256 0.185744587021863 0.183688235605066 0.180393178767648 0.177208294866757 0.174389224194135 0.171923735369984 0.169723351626385 {{us_2025}} 0.16583640931287 0.164092357127913 0.162454545105463 0.160909323261296 0.159448692009461 0.158069206226392 0.156774330726817 0.155566621066295 0.154449705570859 0.153426207060785 0.152496418425382 0.151658383543477 0.150908291406386 0.150241487394684 0.149652671086803 0.149135752743505 0.148685634386723 0.148295241364446 0.147957208168876 0.147664797716698 0.147411985046748 0.147191173081355 0.146995417723395 0.146818253525768 0.146654390040071 +Europe final demand U U shares of total 0.16491345183516 0.160710857760063 0.154355276635327 0.149054613139685 0.145906896593099 0.143775880528747 0.141896860630669 0.140266791187998 0.138026220404039 0.135574967728323 0.132758006582095 0.130102830325263 0.127653579579804 0.125407763844851 0.123338987757727 {{eu_2025}} 0.119622503016436 0.117931399291808 0.116333503569193 0.11482130552861 0.113390051603732 0.112036657179276 0.110761596604602 0.109563494760443 0.108442445566371 0.107398402556524 0.106430277535327 0.105529571878334 0.104692501292963 0.103915786801794 0.103196689222561 0.102532436392848 0.101925752229963 0.101373812203799 0.100874530060506 0.100425596750137 0.100024888835686 9.96627454580256E-02 9.93362033316521E-02 9.90427398919858E-02 9.87799471985866E-02 +ROW final demand U U shares of total 0.521353811329767 0.531180262875026 0.533395108562995 0.540060589453066 0.543278147854063 0.534976807458641 0.537844542074473 0.540098130814746 0.542129192574098 0.544536796666611 0.548548814650257 0.552288874807981 0.555457196226061 0.558068500785166 0.560237660615888 {{row_2025}} 0.563641087670695 0.56497624358028 0.566111951325344 0.567069371210094 0.567861256386808 0.622751279451474 0.625765072971703 0.622194919609123 0.622673325674434 0.622981308570158 0.613273304039291 0.612912044578189 0.612399207300651 0.611742725803522 0.610950639690636 0.610031810863646 0.608988613383314 0.607830946431755 0.606568261770617 0.605209605533165 0.603763126117566 0.602246081460619 0.600668378944953 0.599039006582246 0.597365662761342 diff --git a/examples/1App_GCMat/watts_exec.py b/examples/1App_GCMat/watts_exec.py new file mode 100644 index 00000000..0e324f2f --- /dev/null +++ b/examples/1App_GCMat/watts_exec.py @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: 2022-2023 UChicago Argonne, LLC +# SPDX-License-Identifier: MIT + +""" +This example demonstrates how to use WATTS to run an +GCMAT calculation. This example uses a nuclear +scenario. +""" + +import watts +from pathlib import Path +import numpy as np +import time + + + +params = watts.Parameters() + + +template_name = 'gcmat_template.txt' + + + +final_demand_org = {'2025': 129942.80467537, '2026': 131242.232722123, '2027': 132554.655049345, '2028': 133880.201599838, '2029': 135219.003615836, '2030': 136571.193651995, '2031': 137936.905588515, '2032': 139316.2746444, '2033': 140709.437390844, '2034': 142116.531764752, '2035': 143537.6970824} +china_shares = {'2025': 0.1488, '2026': 0.1509, '2027': 0.153, '2028': 0.1551, '2029': 0.1572, '2030': 0.1593, '2031': 0.1614, '2032': 0.1635, '2033': 0.1656, '2034': 0.1677, '2035': 0.1698} +us_shares = {'2025': 0.167705168506287, '2026': 0.16583640931287, '2027': 0.164092357127913, '2028': 0.162454545105463, '2029': 0.160909323261296, '2030': 0.159448692009461, '2031': 0.158069206226392, '2032': 0.156774330726817, '2033': 0.155566621066295, '2034': 0.154449705570859, '2035': 0.153426207060785} +europe_shares = {'2025': 0.121418171095092, '2026': 0.119622503016436, '2027': 0.117931399291808, '2028': 0.116333503569193, '2029': 0.11482130552861, '2030': 0.113390051603732, '2031': 0.112036657179276, '2032': 0.110761596604602, '2033': 0.109563494760443, '2034': 0.108442445566371, '2035': 0.107398402556524} +row_shares = {'2025': 0.562076660398621, '2026': 0.563641087670695, '2027': 0.56497624358028, '2028': 0.566111951325344, '2029': 0.567069371210094, '2030': 0.567861256386808, '2031': 0.622751279451474, '2032': 0.625765072971703, '2033': 0.622194919609123, '2034': 0.622673325674434, '2035': 0.622981308570158} + +#2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 +#2.935E+07 2.921E+07 3.123E+07 3.134E+07 3.770E+07 3.991E+07 3.149E+07 3.611E+07 3.402E+07 3.372E+07 3.368E+07 + +us_new_demands = {'2025': 293500, '2026': 292100, '2027': 312300, '2028': 313400, '2029': 377000, '2030': 399100, '2031': 314900, '2032': 361100, '2033': 340200, '2034': 337200, '2035': 336800} +#from 2025 to 2035 +# for i in range(2025, 2036): +# now only try 2025 +for i in range(2025, 2026): + china_demand = final_demand_org[str(i)] * china_shares[str(i)] + us_demand = final_demand_org[str(i)] * us_shares[str(i)] + europe_demand = final_demand_org[str(i)] * europe_shares[str(i)] + row_demand = final_demand_org[str(i)] * row_shares[str(i)] + us_new_demand = us_new_demands[str(i)] + new_final_demand = china_demand + europe_demand + row_demand + us_new_demand + params[f'final_demand_{i}'] = new_final_demand + params[f'china_{i}'] = china_demand/new_final_demand + params[f'us_{i}'] = us_demand/new_final_demand + params[f'eu_{i}'] = europe_demand/new_final_demand + params[f'row_{i}'] = row_demand/new_final_demand + +# params.show_summary(show_metadata=True, sort_by='key') +# accert_plugin = watts.PluginACCERT(input_name) +# accert_result = accert_plugin(params) +# gcmat_plugin = watts.PluginGCMAT('gcmat_template.txt', show_stdout=True, show_stderr=True) +# # run command is ./run_repast.sh $'1\tendAt\t2050' $(realpath .) testout +# gcmat_result = gcmat_plugin(params) + + + +# Create a directory for storing results +results_path = Path.cwd() / 'results' +results_path.mkdir(exist_ok=True, parents=True) + +# Set the default path for the database +watts.Database.set_default_path(results_path) +print('results_path',results_path) +# Define some example parameter variations +end_years = [2040, 2050, 2060, 2070] +output_folders = [f"output_{year}" for year in end_years] + +# Start timing the simulation +start = time.perf_counter() + +# Loop through the variations and run the simulations +for end_year, output_folder in zip(end_years, output_folders): + params['end_year'] = end_year + params['output_folder'] = output_folder + params['DATABASE_NAME'] = f'GCMAT_{end_year}.db' + params.show_summary(show_metadata=True, sort_by='key') + # Create the GCMAT plugin + gcmat_plugin = watts.PluginGCMAT('gcmat_template.txt',show_stdout=False, show_stderr=True) + + # Run the simulation + gcmat_result = gcmat_plugin(params, end_year=params['end_year'], output_folder=params['output_folder']) + print('gcmat_result',gcmat_result.csv_data) + +# End timing the simulation +end = time.perf_counter() + +print(f'TOTAL SIMULATION TIME: {np.round((end - start) / 60, 2)} minutes') + diff --git a/src/watts/__init__.py b/src/watts/__init__.py index f63f2727..83c0adad 100644 --- a/src/watts/__init__.py +++ b/src/watts/__init__.py @@ -12,6 +12,7 @@ from .plugin_serpent import * from .plugin_abce import * from .plugin_dakota import * +from .plugin_gcmat import * from .results import * from .template import * from .parameters import * diff --git a/src/watts/plugin_gcmat.py b/src/watts/plugin_gcmat.py new file mode 100644 index 00000000..bc0caaed --- /dev/null +++ b/src/watts/plugin_gcmat.py @@ -0,0 +1,147 @@ +# SPDX-FileCopyrightText: 2024 UChicago Argonne, LLC +# SPDX-License-Identifier: MIT + +from pathlib import Path +import shutil +import subprocess +from typing import List, Optional +import pandas as pd +import sys + +from .plugin import Plugin, PluginGeneric, _find_executable +from .results import Results, ExecInfo +from .fileutils import PathLike +from .parameters import Parameters +from .template import TemplateRenderer +import os + + +class ResultsGCMAT(Results): + """GCMAT simulation results + + Parameters + ---------- + params + Parameters used to generate inputs + exec_info + Execution information (job ID, plugin name, time, etc.) + inputs + List of input files + outputs + List of output files + + Attributes + ---------- + stdout + Standard output from GCMAT run + csv_data + Data from the output CSV file + """ + 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_gcmat_csv_data() + + + def _get_gcmat_csv_data(self) -> pd.DataFrame: + """Read GCMAT output CSV file and return results as a DataFrame""" + output_file = next((p for p in self.outputs if p.name == 'GUIOutputs.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 PluginGCMAT(Plugin): + """Plugin for running GCMAT + + Parameters + ---------- + template_file + Template file used to generate the input files + extra_inputs + Extra (non-templated) input files + show_stdout + Whether to display output from stdout when GCMAT is run + show_stderr + Whether to display output from stderr when GCMAT is run + + """ + def __init__(self, template_file: PathLike, extra_inputs: Optional[List[PathLike]] = None, + show_stdout: bool = False, show_stderr: bool = False): + super().__init__(extra_inputs, show_stdout, show_stderr) + self.template_file = template_file + self.plugin_name = 'GCMAT' + self.renderer = TemplateRenderer(template_file) + self.gcmat_dir = os.getenv('GCMAT_DIR') + if not self.gcmat_dir: + raise EnvironmentError("GCMAT_DIR environment variable is not set.") + + # Include './run_repast.sh' as the executable and all files in the 'data' folder as default extra inputs + self.executable = Path(self.gcmat_dir) / "run_repast.sh" + self.default_extra_inputs = list((Path(self.gcmat_dir) / "complete_model" / "data").glob("**/*")) + # Initialize output_folder attribute + self.output_folder = None + + def prerun(self, params: Parameters) -> None: + """Generate GCMAT input files + + Parameters + ---------- + params + Parameters used by the GCMAT template + """ + # Render the template to create the input file + + input_file = Path("gc_input.txt") + self.renderer(params, filename=input_file) + # print the input file + # Copy the input file to the required directory + model_directory = Path(self.gcmat_dir) / "complete_model" + target_directory = model_directory / "data/scenariosNuclear/default_UserInputs" + target_directory.mkdir(parents=True, exist_ok=True) + #make sure if the file already exists, still copy it + shutil.copy(input_file, target_directory / "demandScenarioV2.txt") + + + def run(self, end_year: int = 2080, output_folder: str = "testout", **kwargs): + """Run GCMAT + + Parameters + ---------- + end_year + The year to end the simulation + output_folder + The folder where outputs will be stored + kwargs + Additional keyword arguments to pass to the subprocess + """ + self.output_folder = output_folder + param_string = f'1\tendAt\t{end_year}' + command = [str(self.executable), param_string, subprocess.check_output('realpath .', shell=True).strip().decode('utf-8'), output_folder] + # Run the GCMAT simulation + subprocess.run(command, cwd=self.gcmat_dir, **kwargs) + + def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsGCMAT: + """Collect information from GCMAT simulation and create results object + + Parameters + ---------- + params + Parameters used to create GCMAT model + exec_info + Execution information + + Returns + ------- + GCMAT results object + """ + output_folder = Path(self.output_folder) # Retrieve the stored output_folder value + + # Only collect the GUIOutputs.csv file + # can add more files if needed + outputs = [] + gui_outputs_file = output_folder / "GUIOutputs.csv" + if gui_outputs_file.exists(): + outputs.append(gui_outputs_file) + + return ResultsGCMAT(params, exec_info, self.extra_inputs, outputs) From 6aac883569c6dfbb1b15ba64c782e2eb122ea1bd Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:11:03 -0500 Subject: [PATCH 02/15] simplified --- CHANGELOG.md | 4 ++++ examples/1App_GCMat/gcmat_template.txt | 10 ++++---- examples/1App_GCMat/watts_exec.py | 33 ++++++-------------------- src/watts/plugin_gcmat.py | 22 +++++++---------- 4 files changed, 25 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0ffe0db..cc6a32fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added +* GCMAT plugin via the `PluginGCMAT` class + +### Added + * The `Plugin.__call__` method now accepts an `output_dir` argument that specifies the directory created in the database ([#107](https://github.com/watts-dev/watts/pull/107)) diff --git a/examples/1App_GCMat/gcmat_template.txt b/examples/1App_GCMat/gcmat_template.txt index 560ff4fc..f750f0d6 100644 --- a/examples/1App_GCMat/gcmat_template.txt +++ b/examples/1App_GCMat/gcmat_template.txt @@ -1,6 +1,6 @@ region final demand agent final demand product reference product unit 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 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 min max - final demand U U U tonnes 111847.841748839 112977.61792812 114118.805988 115271.5212 116435.88 117612 118800 120000 121200 122412 123636.12 124872.4812 126121.206012 127382.41807212 128656.242252841 {{final_demand_2025}} 131242.232722123 132554.655049345 133880.201599838 135219.003615836 136571.193651995 137936.905588515 139316.2746444 140709.437390844 142116.531764752 143537.6970824 144973.074053224 146422.804793756 147887.032841694 149365.903170111 150859.562201812 152368.15782383 153891.839402068 155430.757796089 156985.06537405 158554.91602779 160140.465188068 161741.869839949 163359.288538348 164992.881423732 166642.810237969 -China final demand U U shares of total 0.107142857142857 0.106698999696878 0.112674964564139 0.114434523188336 0.116194081812533 0.1278 0.1299 0.132 0.1341 0.1362 0.1383 0.1404 0.1425 0.1446 0.1467 {{china_2025}} 0.1509 0.153 0.1551 0.1572 0.1593 0.107142857142857 0.106698999696878 0.112674964564139 0.114434523188336 0.116194081812533 0.1278 0.1299 0.132 0.1341 0.1362 0.1383 0.1404 0.1425 0.1446 0.1467 0.1488 0.1509 0.153 0.1551 0.1572 -US final demand U U shares of total 0.206589879692216 0.201409879668034 0.199574650237538 0.196450274218913 0.194620873740305 0.193447312012611 0.190358597294858 0.187635077997256 0.185744587021863 0.183688235605066 0.180393178767648 0.177208294866757 0.174389224194135 0.171923735369984 0.169723351626385 {{us_2025}} 0.16583640931287 0.164092357127913 0.162454545105463 0.160909323261296 0.159448692009461 0.158069206226392 0.156774330726817 0.155566621066295 0.154449705570859 0.153426207060785 0.152496418425382 0.151658383543477 0.150908291406386 0.150241487394684 0.149652671086803 0.149135752743505 0.148685634386723 0.148295241364446 0.147957208168876 0.147664797716698 0.147411985046748 0.147191173081355 0.146995417723395 0.146818253525768 0.146654390040071 -Europe final demand U U shares of total 0.16491345183516 0.160710857760063 0.154355276635327 0.149054613139685 0.145906896593099 0.143775880528747 0.141896860630669 0.140266791187998 0.138026220404039 0.135574967728323 0.132758006582095 0.130102830325263 0.127653579579804 0.125407763844851 0.123338987757727 {{eu_2025}} 0.119622503016436 0.117931399291808 0.116333503569193 0.11482130552861 0.113390051603732 0.112036657179276 0.110761596604602 0.109563494760443 0.108442445566371 0.107398402556524 0.106430277535327 0.105529571878334 0.104692501292963 0.103915786801794 0.103196689222561 0.102532436392848 0.101925752229963 0.101373812203799 0.100874530060506 0.100425596750137 0.100024888835686 9.96627454580256E-02 9.93362033316521E-02 9.90427398919858E-02 9.87799471985866E-02 -ROW final demand U U shares of total 0.521353811329767 0.531180262875026 0.533395108562995 0.540060589453066 0.543278147854063 0.534976807458641 0.537844542074473 0.540098130814746 0.542129192574098 0.544536796666611 0.548548814650257 0.552288874807981 0.555457196226061 0.558068500785166 0.560237660615888 {{row_2025}} 0.563641087670695 0.56497624358028 0.566111951325344 0.567069371210094 0.567861256386808 0.622751279451474 0.625765072971703 0.622194919609123 0.622673325674434 0.622981308570158 0.613273304039291 0.612912044578189 0.612399207300651 0.611742725803522 0.610950639690636 0.610031810863646 0.608988613383314 0.607830946431755 0.606568261770617 0.605209605533165 0.603763126117566 0.602246081460619 0.600668378944953 0.599039006582246 0.597365662761342 + final demand U U U tonnes 111847.841748839 112977.61792812 114118.805988 115271.5212 116435.88 117612 118800 120000 121200 122412 123636.12 124872.4812 126121.206012 127382.41807212 128656.242252841 {{final_demand_2025}} {{final_demand_2026}} {{final_demand_2027}} {{final_demand_2028}} {{final_demand_2029}} {{final_demand_2030}} {{final_demand_2031}} {{final_demand_2032}} {{final_demand_2033}} {{final_demand_2034}} {{final_demand_2035}} 144973.074053224 146422.804793756 147887.032841694 149365.903170111 150859.562201812 152368.15782383 153891.839402068 155430.757796089 156985.06537405 158554.91602779 160140.465188068 161741.869839949 163359.288538348 164992.881423732 166642.810237969 +China final demand U U shares of total 0.107142857142857 0.106698999696878 0.112674964564139 0.114434523188336 0.116194081812533 0.1278 0.1299 0.132 0.1341 0.1362 0.1383 0.1404 0.1425 0.1446 0.1467 {{china_2025}} {{china_2026}} {{china_2027}} {{china_2028}} {{china_2029}} {{china_2030}} {{china_2031}} {{china_2032}} {{china_2033}} {{china_2034}} {{china_2035}} 0.1278 0.1299 0.132 0.1341 0.1362 0.1383 0.1404 0.1425 0.1446 0.1467 0.1488 0.1509 0.153 0.1551 0.1572 +US final demand U U shares of total 0.206589879692216 0.201409879668034 0.199574650237538 0.196450274218913 0.194620873740305 0.193447312012611 0.190358597294858 0.187635077997256 0.185744587021863 0.183688235605066 0.180393178767648 0.177208294866757 0.174389224194135 0.171923735369984 0.169723351626385 {{us_2025}} {{us_2026}} {{us_2027}} {{us_2028}} {{us_2029}} {{us_2030}} {{us_2031}} {{us_2032}} {{us_2033}} {{us_2034}} {{us_2035}} 0.152496418425382 0.151658383543477 0.150908291406386 0.150241487394684 0.149652671086803 0.149135752743505 0.148685634386723 0.148295241364446 0.147957208168876 0.147664797716698 0.147411985046748 0.147191173081355 0.146995417723395 0.146818253525768 0.146654390040071 +Europe final demand U U shares of total 0.16491345183516 0.160710857760063 0.154355276635327 0.149054613139685 0.145906896593099 0.143775880528747 0.141896860630669 0.140266791187998 0.138026220404039 0.135574967728323 0.132758006582095 0.130102830325263 0.127653579579804 0.125407763844851 0.123338987757727 {{eu_2025}} {{eu_2026}} {{eu_2027}} {{eu_2028}} {{eu_2029}} {{eu_2030}} {{eu_2031}} {{eu_2032}} {{eu_2033}} {{eu_2034}} {{eu_2035}} 0.106430277535327 0.105529571878334 0.104692501292963 0.103915786801794 0.103196689222561 0.102532436392848 0.101925752229963 0.101373812203799 0.100874530060506 0.100425596750137 0.100024888835686 9.96627454580256E-02 9.93362033316521E-02 9.90427398919858E-02 9.87799471985866E-02 +ROW final demand U U shares of total 0.521353811329767 0.531180262875026 0.533395108562995 0.540060589453066 0.543278147854063 0.534976807458641 0.537844542074473 0.540098130814746 0.542129192574098 0.544536796666611 0.548548814650257 0.552288874807981 0.555457196226061 0.558068500785166 0.560237660615888 {{row_2025}} {{row_2026}} {{row_2027}} {{row_2028}} {{row_2029}} {{row_2030}} {{row_2031}} {{row_2032}} {{row_2033}} {{row_2034}} {{row_2035}} 0.613273304039291 0.612912044578189 0.612399207300651 0.611742725803522 0.610950639690636 0.610031810863646 0.608988613383314 0.607830946431755 0.606568261770617 0.605209605533165 0.603763126117566 0.602246081460619 0.600668378944953 0.599039006582246 0.597365662761342 diff --git a/examples/1App_GCMat/watts_exec.py b/examples/1App_GCMat/watts_exec.py index 0e324f2f..3d9a8bdf 100644 --- a/examples/1App_GCMat/watts_exec.py +++ b/examples/1App_GCMat/watts_exec.py @@ -12,29 +12,19 @@ import numpy as np import time - - params = watts.Parameters() - - template_name = 'gcmat_template.txt' - - final_demand_org = {'2025': 129942.80467537, '2026': 131242.232722123, '2027': 132554.655049345, '2028': 133880.201599838, '2029': 135219.003615836, '2030': 136571.193651995, '2031': 137936.905588515, '2032': 139316.2746444, '2033': 140709.437390844, '2034': 142116.531764752, '2035': 143537.6970824} china_shares = {'2025': 0.1488, '2026': 0.1509, '2027': 0.153, '2028': 0.1551, '2029': 0.1572, '2030': 0.1593, '2031': 0.1614, '2032': 0.1635, '2033': 0.1656, '2034': 0.1677, '2035': 0.1698} us_shares = {'2025': 0.167705168506287, '2026': 0.16583640931287, '2027': 0.164092357127913, '2028': 0.162454545105463, '2029': 0.160909323261296, '2030': 0.159448692009461, '2031': 0.158069206226392, '2032': 0.156774330726817, '2033': 0.155566621066295, '2034': 0.154449705570859, '2035': 0.153426207060785} europe_shares = {'2025': 0.121418171095092, '2026': 0.119622503016436, '2027': 0.117931399291808, '2028': 0.116333503569193, '2029': 0.11482130552861, '2030': 0.113390051603732, '2031': 0.112036657179276, '2032': 0.110761596604602, '2033': 0.109563494760443, '2034': 0.108442445566371, '2035': 0.107398402556524} row_shares = {'2025': 0.562076660398621, '2026': 0.563641087670695, '2027': 0.56497624358028, '2028': 0.566111951325344, '2029': 0.567069371210094, '2030': 0.567861256386808, '2031': 0.622751279451474, '2032': 0.625765072971703, '2033': 0.622194919609123, '2034': 0.622673325674434, '2035': 0.622981308570158} -#2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 -#2.935E+07 2.921E+07 3.123E+07 3.134E+07 3.770E+07 3.991E+07 3.149E+07 3.611E+07 3.402E+07 3.372E+07 3.368E+07 - us_new_demands = {'2025': 293500, '2026': 292100, '2027': 312300, '2028': 313400, '2029': 377000, '2030': 399100, '2031': 314900, '2032': 361100, '2033': 340200, '2034': 337200, '2035': 336800} -#from 2025 to 2035 -# for i in range(2025, 2036): -# now only try 2025 -for i in range(2025, 2026): + +# example from 2025 to 2035 +for i in range(2025, 2036): china_demand = final_demand_org[str(i)] * china_shares[str(i)] us_demand = final_demand_org[str(i)] * us_shares[str(i)] europe_demand = final_demand_org[str(i)] * europe_shares[str(i)] @@ -47,15 +37,6 @@ params[f'eu_{i}'] = europe_demand/new_final_demand params[f'row_{i}'] = row_demand/new_final_demand -# params.show_summary(show_metadata=True, sort_by='key') -# accert_plugin = watts.PluginACCERT(input_name) -# accert_result = accert_plugin(params) -# gcmat_plugin = watts.PluginGCMAT('gcmat_template.txt', show_stdout=True, show_stderr=True) -# # run command is ./run_repast.sh $'1\tendAt\t2050' $(realpath .) testout -# gcmat_result = gcmat_plugin(params) - - - # Create a directory for storing results results_path = Path.cwd() / 'results' results_path.mkdir(exist_ok=True, parents=True) @@ -64,7 +45,7 @@ watts.Database.set_default_path(results_path) print('results_path',results_path) # Define some example parameter variations -end_years = [2040, 2050, 2060, 2070] +end_years = [2040,2080] output_folders = [f"output_{year}" for year in end_years] # Start timing the simulation @@ -77,14 +58,14 @@ params['DATABASE_NAME'] = f'GCMAT_{end_year}.db' params.show_summary(show_metadata=True, sort_by='key') # Create the GCMAT plugin - gcmat_plugin = watts.PluginGCMAT('gcmat_template.txt',show_stdout=False, show_stderr=True) + gcmat_plugin = watts.PluginGCMAT('gcmat_template.txt',show_stdout=True, show_stderr=True) # Run the simulation gcmat_result = gcmat_plugin(params, end_year=params['end_year'], output_folder=params['output_folder']) - print('gcmat_result',gcmat_result.csv_data) + # print the end year price of the gcmat_result.csv_data + print(f'end year {end_year} price: {gcmat_result.csv_data["U buyer price US"].iloc[-1]}') # End timing the simulation end = time.perf_counter() print(f'TOTAL SIMULATION TIME: {np.round((end - start) / 60, 2)} minutes') - diff --git a/src/watts/plugin_gcmat.py b/src/watts/plugin_gcmat.py index bc0caaed..27c0d38b 100644 --- a/src/watts/plugin_gcmat.py +++ b/src/watts/plugin_gcmat.py @@ -5,16 +5,14 @@ import shutil import subprocess from typing import List, Optional +import os import pandas as pd -import sys -from .plugin import Plugin, PluginGeneric, _find_executable +from .plugin import Plugin from .results import Results, ExecInfo from .fileutils import PathLike from .parameters import Parameters from .template import TemplateRenderer -import os - class ResultsGCMAT(Results): """GCMAT simulation results @@ -42,7 +40,6 @@ def __init__(self, params: Parameters, exec_info: ExecInfo, super().__init__(params, exec_info, inputs, outputs) self.csv_data = self._get_gcmat_csv_data() - def _get_gcmat_csv_data(self) -> pd.DataFrame: """Read GCMAT output CSV file and return results as a DataFrame""" output_file = next((p for p in self.outputs if p.name == 'GUIOutputs.csv'), None) @@ -79,7 +76,8 @@ def __init__(self, template_file: PathLike, extra_inputs: Optional[List[PathLike # Include './run_repast.sh' as the executable and all files in the 'data' folder as default extra inputs self.executable = Path(self.gcmat_dir) / "run_repast.sh" self.default_extra_inputs = list((Path(self.gcmat_dir) / "complete_model" / "data").glob("**/*")) - # Initialize output_folder attribute + + # Initialize output_folder attribute self.output_folder = None def prerun(self, params: Parameters) -> None: @@ -91,15 +89,13 @@ def prerun(self, params: Parameters) -> None: Parameters used by the GCMAT template """ # Render the template to create the input file - input_file = Path("gc_input.txt") self.renderer(params, filename=input_file) - # print the input file + # Copy the input file to the required directory model_directory = Path(self.gcmat_dir) / "complete_model" target_directory = model_directory / "data/scenariosNuclear/default_UserInputs" target_directory.mkdir(parents=True, exist_ok=True) - #make sure if the file already exists, still copy it shutil.copy(input_file, target_directory / "demandScenarioV2.txt") @@ -115,7 +111,8 @@ def run(self, end_year: int = 2080, output_folder: str = "testout", **kwargs): kwargs Additional keyword arguments to pass to the subprocess """ - self.output_folder = output_folder + # use the absolute path for the output folder + self.output_folder = os.path.join(self.gcmat_dir, output_folder) param_string = f'1\tendAt\t{end_year}' command = [str(self.executable), param_string, subprocess.check_output('realpath .', shell=True).strip().decode('utf-8'), output_folder] # Run the GCMAT simulation @@ -135,13 +132,12 @@ def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsGCMAT: ------- GCMAT results object """ - output_folder = Path(self.output_folder) # Retrieve the stored output_folder value - + output_folder = Path(self.output_folder) # Retrieve the stored # Only collect the GUIOutputs.csv file # can add more files if needed outputs = [] gui_outputs_file = output_folder / "GUIOutputs.csv" if gui_outputs_file.exists(): outputs.append(gui_outputs_file) - return ResultsGCMAT(params, exec_info, self.extra_inputs, outputs) + \ No newline at end of file From 20db5a6dc7573c9b57c96817ad3225fc7bee6e92 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 18 Apr 2024 10:44:25 -0500 Subject: [PATCH 03/15] Update versions for GHA actions --- .github/workflows/ci.yml | 2 +- .github/workflows/publish-pypi.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c1bd15f..6d82e3fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: container: openmc/openmc:latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Apt dependencies shell: bash diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index cf01afee..390560bb 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -10,9 +10,9 @@ jobs: permissions: id-token: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install dependencies From 353008d76ee58d1ebbbee158a3f7e8758990e132 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 18 Apr 2024 11:05:24 -0500 Subject: [PATCH 04/15] Update action for publishing to PyPI --- .github/workflows/publish-pypi.yml | 32 ++++++++++++++++++------------ pyproject.toml | 1 + 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 390560bb..ba033def 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -5,21 +5,27 @@ on: types: [published] jobs: - deploy: + dist: runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: hynek/build-and-inspect-python-package@v2 + + publish: + needs: [dist] + environment: pypi permissions: id-token: write + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - uses: actions/download-artifact@v4 with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish a Python distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + name: Packages + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/pyproject.toml b/pyproject.toml index 98fc069d..5671b3a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] requires-python = ">=3.7" dependencies = [ From a2101ec1ecd28c27497360588523bda55b4390f7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 18 Apr 2024 11:16:08 -0500 Subject: [PATCH 05/15] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc6a32fa..79ad2215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,12 +17,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. specifies the directory created in the database ([#107](https://github.com/watts-dev/watts/pull/107)) +### Changes + +* Update recommendations for Dakota version + ([#111](https://github.com/watts-dev/watts/pull/111)) +* Update GitHub Actions workflows ([#112](https://github.com/watts-dev/watts/pull/112)) + ### Fixed * Fixed path to system python executable in dakota plugin * Fixed path for templates rendered from the `extra_template_inputs` argument in `PluginGeneric` and subclasses ([#109](https://github.com/watts-dev/watts/pull/109)) +* Update to fix `PluginDakota` executable + ([#110](https://github.com/watts-dev/watts/pull/110)) ## [0.5.1] From fd5dec5dd0bccb87da54a74e020bb80068bc2c6e Mon Sep 17 00:00:00 2001 From: danieletimpano Date: Thu, 30 May 2024 11:24:59 -0500 Subject: [PATCH 06/15] SAS plugin modifications to handle MFUEL output. --- src/watts/plugin_sas.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/watts/plugin_sas.py b/src/watts/plugin_sas.py index 51df5218..3c2f7d38 100644 --- a/src/watts/plugin_sas.py +++ b/src/watts/plugin_sas.py @@ -94,6 +94,8 @@ class PluginSAS(PluginGeneric): List of command-line arguments used to call the executable conv_channel Path to CHANNELtoCSV utility executable + conv_mfuel (Timpano - May 2024: modifications to work with MFUEL output) + Path to MFUELPeaktoCSV utility executable conv_primar4 Path to PRIMAR4toCSV utility executable @@ -118,6 +120,7 @@ def __init__( # Set other executables based on the main SAS executable suffix = executable.suffix self._conv_channel = executable.with_name(f"CHANNELtoCSV{suffix}") + self._conv_mfuel = executable.with_name(f"MFUELtoCSV{suffix}") # Timpano - May 2024: modifications to work with MFUEL output self._conv_primar4 = executable.with_name(f"PRIMAR4toCSV{suffix}") @property @@ -128,6 +131,10 @@ def conv_channel(self) -> Path: def conv_primar4(self) -> Path: return self._conv_primar4 + @property + def conv_mfuel(self) -> Path: # Timpano - May 2024: modifications to work with MFUEL output + return self._conv_mfuel + @conv_channel.setter def conv_channel(self, exe: PathLike): if shutil.which(exe) is None: @@ -140,6 +147,12 @@ def conv_primar4(self, exe: PathLike): raise RuntimeError(f"PRIMAR4toCSV utility executable '{exe}' is missing.") self._conv_primar4 = Path(exe) + @conv_mfuel.setter + def conv_mfuel(self, exe: PathLike): # Timpano - May 2024: modifications to work with MFUEL output + if shutil.which(exe) is None: + raise RuntimeError(f"MFUELtoCSV utility executable '{exe}' is missing.") + self._conv_mfuel = Path(exe) + @property def execute_command(self): return [str(self.executable), "-i", self.input_name, "-o", "out.txt"] @@ -159,9 +172,10 @@ def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsSAS: SAS results object """ - # Convert CHANNEl.dat and PRIMER4.dat to csv files + # Convert CHANNEl.dat and PRIMAR4.dat and MFUELss.dat to csv files # using SAS utilities. Check if files exist because # they may not be outputted per user's choice. + if Path("CHANNEL.dat").is_file(): with open("CHANNEL.dat", "r") as file_in, open("CHANNEL.csv", "w") as file_out: subprocess.run(str(self.conv_channel), stdin=file_in, stdout=file_out) @@ -170,4 +184,8 @@ def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsSAS: with open("PRIMAR4.dat", "r") as file_in, open("PRIMAR4.csv", "w") as file_out: subprocess.run(str(self.conv_primar4), stdin=file_in, stdout=file_out) + if Path("MFUELss_C000001.dat").is_file(): # Timpano - May 2024: modifications to work with MFUEL output + with open("MFUELss_C000001.dat", "r") as file_in: + subprocess.run(str(self.conv_mfuel), stdin=file_in) + return super().postrun(params, exec_info) From 69818a727bb40b55705b061e827d1f6bdcb80c67 Mon Sep 17 00:00:00 2001 From: Daniele Timpano Date: Fri, 31 May 2024 09:43:29 -0500 Subject: [PATCH 07/15] Update plugin_sas.py Comments removed as indicated. --- src/watts/plugin_sas.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/watts/plugin_sas.py b/src/watts/plugin_sas.py index 3c2f7d38..a431b9c8 100644 --- a/src/watts/plugin_sas.py +++ b/src/watts/plugin_sas.py @@ -94,7 +94,7 @@ class PluginSAS(PluginGeneric): List of command-line arguments used to call the executable conv_channel Path to CHANNELtoCSV utility executable - conv_mfuel (Timpano - May 2024: modifications to work with MFUEL output) + conv_mfuel Path to MFUELPeaktoCSV utility executable conv_primar4 Path to PRIMAR4toCSV utility executable @@ -120,7 +120,7 @@ def __init__( # Set other executables based on the main SAS executable suffix = executable.suffix self._conv_channel = executable.with_name(f"CHANNELtoCSV{suffix}") - self._conv_mfuel = executable.with_name(f"MFUELtoCSV{suffix}") # Timpano - May 2024: modifications to work with MFUEL output + self._conv_mfuel = executable.with_name(f"MFUELtoCSV{suffix}") self._conv_primar4 = executable.with_name(f"PRIMAR4toCSV{suffix}") @property @@ -132,7 +132,7 @@ def conv_primar4(self) -> Path: return self._conv_primar4 @property - def conv_mfuel(self) -> Path: # Timpano - May 2024: modifications to work with MFUEL output + def conv_mfuel(self) -> Path: return self._conv_mfuel @conv_channel.setter @@ -148,7 +148,7 @@ def conv_primar4(self, exe: PathLike): self._conv_primar4 = Path(exe) @conv_mfuel.setter - def conv_mfuel(self, exe: PathLike): # Timpano - May 2024: modifications to work with MFUEL output + def conv_mfuel(self, exe: PathLike): if shutil.which(exe) is None: raise RuntimeError(f"MFUELtoCSV utility executable '{exe}' is missing.") self._conv_mfuel = Path(exe) @@ -184,7 +184,7 @@ def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsSAS: with open("PRIMAR4.dat", "r") as file_in, open("PRIMAR4.csv", "w") as file_out: subprocess.run(str(self.conv_primar4), stdin=file_in, stdout=file_out) - if Path("MFUELss_C000001.dat").is_file(): # Timpano - May 2024: modifications to work with MFUEL output + if Path("MFUELss_C000001.dat").is_file(): with open("MFUELss_C000001.dat", "r") as file_in: subprocess.run(str(self.conv_mfuel), stdin=file_in) From d0ea3512223c71087c860b2db23253d53b6a1661 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:36:07 -0500 Subject: [PATCH 08/15] retouch and rebase --- CHANGELOG.md | 2 +- doc/source/reference/index.rst | 1 + doc/source/user/plugins.rst | 33 +++++++++++++++++++++++++++++++++ examples/1App_GCMat/README.md | 17 ++++++++--------- src/watts/plugin_gcmat.py | 3 ++- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79ad2215..334fd564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -* GCMAT plugin via the `PluginGCMAT` class +* GCMAT plugin via the `PluginGCMAT` class ([114](https://github.com/watts-dev/watts/pull/114)) ### Added diff --git a/doc/source/reference/index.rst b/doc/source/reference/index.rst index 9a70687b..2c83cbf2 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.PluginGCMat watts.PluginMCNP watts.PluginMOOSE watts.PluginOpenMC diff --git a/doc/source/user/plugins.rst b/doc/source/user/plugins.rst index 1cb78055..018a32ba 100644 --- a/doc/source/user/plugins.rst +++ b/doc/source/user/plugins.rst @@ -472,3 +472,36 @@ As with other plugins, :class:`~watts.PluginACCERT` is used by:: accert_plugin = watts.PluginACCERT('accert_template') accert_result = accert_plugin(params) + +GCMat Plugin +++++++++++++ + +The :class:`~watts.PluginGCMat` class enables simulations with Argonne’s global critical materials agent-based model (GCMat) to simulate dynamic economic markets that are composed of agents who have complex decision-making behaviors, and interact with and influence each other, possibly indirectly through market signals. + +The GCMat plugin requires a template input file that can be templated as follows: + + +region final demand agent final demand product reference product unit 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 +final demand U U U tonnes 111847.841748839 112977.61792812 114118.805988 115271.5212 116435.88 117612 118800 120000 121200 122412 123636.12 124872.4812 126121.206012 127382.41807212 128656.242252841 {{final_demand_2025}} {{final_demand_2026}} {{final_demand_2027}} {{final_demand_2028}} {{final_demand_2029}} {{final_demand_2030}} + +China final demand U U shares of total 0.107142857142857 0.106698999696878 0.112674964564139 0.114434523188336 0.116194081812533 0.1278 0.1299 0.132 0.1341 0.1362 0.1383 0.1404 0.1425 0.1446 0.1467 {{china_2025}} {{china_2026}} {{china_2027}} {{china_2028}} {{china_2029}} {{china_2030}} + +US final demand U U shares of total 0.206589879692216 0.201409879668034 0.199574650237538 0.196450274218913 0.194620873740305 0.193447312012611 0.190358597294858 0.187635077997256 0.185744587021863 0.183688235605066 0.180393178767648 0.177208294866757 0.174389224194135 0.171923735369984 0.169723351626385 {{us_2025}} {{us_2026}} {{us_2027}} {{us_2028}} {{us_2029}} {{us_2030}} + +Europe final demand U U shares of total 0.16491345183516 0.160710857760063 0.154355276635327 0.149054613139685 0.145906896593099 0.143775880528747 0.141896860630669 0.140266791187998 0.138026220404039 0.135574967728323 0.132758006582095 0.130102830325263 0.127653579579804 0.125407763844851 0.123338987757727 {{eu_2025}} {{eu_2026}} {{eu_2027}} {{eu_2028}} {{eu_2029}} {{eu_2030}} + +ROW final demand U U shares of total 0.521353811330767 0.531180262874025 0.533394108562996 0.539080588452066 0.543278147354063 0.535073807414382 0.536243130077473 0.536734120790743 0.541204905572035 0.541712831061545 0.548846808065192 0.554835894215335 0.556026422605261 0.556024685007383 0.556929270113103 {{row_2025}} {{row_2026}} {{row_2027}} {{row_2028}} {{row_2029}} {{row_2030}} + + +The GCMat plugin can be instantiated with the following command line:: + + gcmat_plugin = watts.PluginGCMat('gcmat_template') + +Before running the GCMat plugin, the directory that the executable 'run_repast.sh' must be set. This can be done by adding the ``GCMAT_DIR`` variable to the environment:: + + export GCMAT_DIR='/path/to/gcmat/output' + +As with other plugins, :class:`~watts.PluginGCMat` is used by:: + + gcmat_plugin = watts.PluginGCMat('gcmat_template') + gcmat_result = gcmat_plugin(params) diff --git a/examples/1App_GCMat/README.md b/examples/1App_GCMat/README.md index 26dab0f4..89262794 100644 --- a/examples/1App_GCMat/README.md +++ b/examples/1App_GCMat/README.md @@ -1,23 +1,22 @@ -# 1App_ABCE_C2N +# 1App_GCMat ## Purpose -This example provides a demonstration for using WATTS to perform an -agent based capacity expansion with ABCE for a coal-to-nuclear (C2N) -transition scenario. +This example provides a demonstration for using WATTS explore supply chain dynamics and uncertainty under nuclear scenarios of Uranium fuel demand growth or shrinkage, supply disruptions. ## Code(s) -- ABCE -- A-LEAF (ABCE dependency) +- GCMat +- Java (GCMat dependency) +- Repast Simphony agent-based modeling toolkit ## Keywords -- Capacity Expansion +- Rare Earths Supply Chain - Agent Based Modeling -- Electricity Markets +- Dynamic economic markets ## 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. -- [__abce_template__](abce_template.txt): Templated ABCE model. +- [__abce_template__](abce_template.txt): Templated GCMat model for the Uranium demand of nuclear scenarios. diff --git a/src/watts/plugin_gcmat.py b/src/watts/plugin_gcmat.py index 27c0d38b..b8aec528 100644 --- a/src/watts/plugin_gcmat.py +++ b/src/watts/plugin_gcmat.py @@ -63,7 +63,8 @@ class PluginGCMAT(Plugin): Whether to display output from stderr when GCMAT is run """ - def __init__(self, template_file: PathLike, extra_inputs: Optional[List[PathLike]] = None, + def __init__(self, template_file: PathLike, + extra_inputs: Optional[List[PathLike]] = None, show_stdout: bool = False, show_stderr: bool = False): super().__init__(extra_inputs, show_stdout, show_stderr) self.template_file = template_file From 6a58b95ccfeb9419e0a368b8ecba6cc9a08a6ea9 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:54:38 -0500 Subject: [PATCH 09/15] rebase --- src/watts/plugin_gcmat.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/watts/plugin_gcmat.py b/src/watts/plugin_gcmat.py index b8aec528..f39a4098 100644 --- a/src/watts/plugin_gcmat.py +++ b/src/watts/plugin_gcmat.py @@ -140,5 +140,4 @@ def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsGCMAT: gui_outputs_file = output_folder / "GUIOutputs.csv" if gui_outputs_file.exists(): outputs.append(gui_outputs_file) - return ResultsGCMAT(params, exec_info, self.extra_inputs, outputs) - \ No newline at end of file + return ResultsGCMAT(params, exec_info, self.extra_inputs, outputs) \ No newline at end of file From 459a288bff8c16972a279e14405f48b6a61aee41 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:57:52 -0500 Subject: [PATCH 10/15] rebase --- src/watts/plugin_gcmat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/watts/plugin_gcmat.py b/src/watts/plugin_gcmat.py index f39a4098..7b492481 100644 --- a/src/watts/plugin_gcmat.py +++ b/src/watts/plugin_gcmat.py @@ -140,4 +140,4 @@ def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsGCMAT: gui_outputs_file = output_folder / "GUIOutputs.csv" if gui_outputs_file.exists(): outputs.append(gui_outputs_file) - return ResultsGCMAT(params, exec_info, self.extra_inputs, outputs) \ No newline at end of file + return ResultsGCMAT(params, exec_info, self.extra_inputs, outputs) From 41417951c9a1d22adac0b4d01e4ab9f9b060bb13 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:02:14 -0500 Subject: [PATCH 11/15] typo --- examples/1App_GCMat/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/1App_GCMat/README.md b/examples/1App_GCMat/README.md index 89262794..acf3f818 100644 --- a/examples/1App_GCMat/README.md +++ b/examples/1App_GCMat/README.md @@ -19,4 +19,4 @@ This example provides a demonstration for using WATTS explore supply chain dynam ## 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. -- [__abce_template__](abce_template.txt): Templated GCMat model for the Uranium demand of nuclear scenarios. +- [__abce_template__](gcmat_template.txt): Templated GCMat model for the Uranium demand of nuclear scenarios. From 28a098c1b151bd80cde94738ab0f5150394c5ba5 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:03:23 -0500 Subject: [PATCH 12/15] update readme --- examples/1App_GCMat/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/1App_GCMat/README.md b/examples/1App_GCMat/README.md index acf3f818..c98b5708 100644 --- a/examples/1App_GCMat/README.md +++ b/examples/1App_GCMat/README.md @@ -19,4 +19,4 @@ This example provides a demonstration for using WATTS explore supply chain dynam ## 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. -- [__abce_template__](gcmat_template.txt): Templated GCMat model for the Uranium demand of nuclear scenarios. +- [__gcmat_template__](gcmat_template.txt): Templated GCMat model for the Uranium demand of nuclear scenarios. From e8faa3e62906905450fc6b9360c08806ff45df95 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 16 Aug 2024 21:55:18 -0500 Subject: [PATCH 13/15] Style fixes --- CHANGELOG.md | 5 +---- doc/source/user/plugins.rst | 23 +++++++++++++++-------- src/watts/plugin_gcmat.py | 7 ++++--- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 334fd564..72d18faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -* GCMAT plugin via the `PluginGCMAT` class ([114](https://github.com/watts-dev/watts/pull/114)) - -### Added - * The `Plugin.__call__` method now accepts an `output_dir` argument that specifies the directory created in the database ([#107](https://github.com/watts-dev/watts/pull/107)) +* GCMAT plugin via the `PluginGCMAT` class ([114](https://github.com/watts-dev/watts/pull/114)) ### Changes diff --git a/doc/source/user/plugins.rst b/doc/source/user/plugins.rst index 018a32ba..2e2530a6 100644 --- a/doc/source/user/plugins.rst +++ b/doc/source/user/plugins.rst @@ -476,28 +476,35 @@ As with other plugins, :class:`~watts.PluginACCERT` is used by:: GCMat Plugin ++++++++++++ -The :class:`~watts.PluginGCMat` class enables simulations with Argonne’s global critical materials agent-based model (GCMat) to simulate dynamic economic markets that are composed of agents who have complex decision-making behaviors, and interact with and influence each other, possibly indirectly through market signals. +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 +through market signals. The GCMat plugin requires a template input file that can be templated as follows: +.. code-block:: jinja -region final demand agent final demand product reference product unit 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 -final demand U U U tonnes 111847.841748839 112977.61792812 114118.805988 115271.5212 116435.88 117612 118800 120000 121200 122412 123636.12 124872.4812 126121.206012 127382.41807212 128656.242252841 {{final_demand_2025}} {{final_demand_2026}} {{final_demand_2027}} {{final_demand_2028}} {{final_demand_2029}} {{final_demand_2030}} + region final demand agent final demand product reference product unit 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 + final demand U U U tonnes 111847.841748839 112977.61792812 114118.805988 115271.5212 116435.88 117612 118800 120000 121200 122412 123636.12 124872.4812 126121.206012 127382.41807212 128656.242252841 {{final_demand_2025}} {{final_demand_2026}} {{final_demand_2027}} {{final_demand_2028}} {{final_demand_2029}} {{final_demand_2030}} -China final demand U U shares of total 0.107142857142857 0.106698999696878 0.112674964564139 0.114434523188336 0.116194081812533 0.1278 0.1299 0.132 0.1341 0.1362 0.1383 0.1404 0.1425 0.1446 0.1467 {{china_2025}} {{china_2026}} {{china_2027}} {{china_2028}} {{china_2029}} {{china_2030}} + China final demand U U shares of total 0.107142857142857 0.106698999696878 0.112674964564139 0.114434523188336 0.116194081812533 0.1278 0.1299 0.132 0.1341 0.1362 0.1383 0.1404 0.1425 0.1446 0.1467 {{china_2025}} {{china_2026}} {{china_2027}} {{china_2028}} {{china_2029}} {{china_2030}} -US final demand U U shares of total 0.206589879692216 0.201409879668034 0.199574650237538 0.196450274218913 0.194620873740305 0.193447312012611 0.190358597294858 0.187635077997256 0.185744587021863 0.183688235605066 0.180393178767648 0.177208294866757 0.174389224194135 0.171923735369984 0.169723351626385 {{us_2025}} {{us_2026}} {{us_2027}} {{us_2028}} {{us_2029}} {{us_2030}} + US final demand U U shares of total 0.206589879692216 0.201409879668034 0.199574650237538 0.196450274218913 0.194620873740305 0.193447312012611 0.190358597294858 0.187635077997256 0.185744587021863 0.183688235605066 0.180393178767648 0.177208294866757 0.174389224194135 0.171923735369984 0.169723351626385 {{us_2025}} {{us_2026}} {{us_2027}} {{us_2028}} {{us_2029}} {{us_2030}} -Europe final demand U U shares of total 0.16491345183516 0.160710857760063 0.154355276635327 0.149054613139685 0.145906896593099 0.143775880528747 0.141896860630669 0.140266791187998 0.138026220404039 0.135574967728323 0.132758006582095 0.130102830325263 0.127653579579804 0.125407763844851 0.123338987757727 {{eu_2025}} {{eu_2026}} {{eu_2027}} {{eu_2028}} {{eu_2029}} {{eu_2030}} + Europe final demand U U shares of total 0.16491345183516 0.160710857760063 0.154355276635327 0.149054613139685 0.145906896593099 0.143775880528747 0.141896860630669 0.140266791187998 0.138026220404039 0.135574967728323 0.132758006582095 0.130102830325263 0.127653579579804 0.125407763844851 0.123338987757727 {{eu_2025}} {{eu_2026}} {{eu_2027}} {{eu_2028}} {{eu_2029}} {{eu_2030}} -ROW final demand U U shares of total 0.521353811330767 0.531180262874025 0.533394108562996 0.539080588452066 0.543278147354063 0.535073807414382 0.536243130077473 0.536734120790743 0.541204905572035 0.541712831061545 0.548846808065192 0.554835894215335 0.556026422605261 0.556024685007383 0.556929270113103 {{row_2025}} {{row_2026}} {{row_2027}} {{row_2028}} {{row_2029}} {{row_2030}} + ROW final demand U U shares of total 0.521353811330767 0.531180262874025 0.533394108562996 0.539080588452066 0.543278147354063 0.535073807414382 0.536243130077473 0.536734120790743 0.541204905572035 0.541712831061545 0.548846808065192 0.554835894215335 0.556026422605261 0.556024685007383 0.556929270113103 {{row_2025}} {{row_2026}} {{row_2027}} {{row_2028}} {{row_2029}} {{row_2030}} The GCMat plugin can be instantiated with the following command line:: gcmat_plugin = watts.PluginGCMat('gcmat_template') -Before running the GCMat plugin, the directory that the executable 'run_repast.sh' must be set. This can be done by adding the ``GCMAT_DIR`` variable to the environment:: +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`` +environment variable:: export GCMAT_DIR='/path/to/gcmat/output' diff --git a/src/watts/plugin_gcmat.py b/src/watts/plugin_gcmat.py index 7b492481..6656cbc6 100644 --- a/src/watts/plugin_gcmat.py +++ b/src/watts/plugin_gcmat.py @@ -14,6 +14,7 @@ from .parameters import Parameters from .template import TemplateRenderer + class ResultsGCMAT(Results): """GCMAT simulation results @@ -48,6 +49,7 @@ def _get_gcmat_csv_data(self) -> pd.DataFrame: else: return pd.DataFrame() # Return an empty DataFrame if no CSV file is found + class PluginGCMAT(Plugin): """Plugin for running GCMAT @@ -77,7 +79,7 @@ def __init__(self, template_file: PathLike, # Include './run_repast.sh' as the executable and all files in the 'data' folder as default extra inputs self.executable = Path(self.gcmat_dir) / "run_repast.sh" self.default_extra_inputs = list((Path(self.gcmat_dir) / "complete_model" / "data").glob("**/*")) - + # Initialize output_folder attribute self.output_folder = None @@ -99,7 +101,6 @@ def prerun(self, params: Parameters) -> None: target_directory.mkdir(parents=True, exist_ok=True) shutil.copy(input_file, target_directory / "demandScenarioV2.txt") - def run(self, end_year: int = 2080, output_folder: str = "testout", **kwargs): """Run GCMAT @@ -133,7 +134,7 @@ def postrun(self, params: Parameters, exec_info: ExecInfo) -> ResultsGCMAT: ------- GCMAT results object """ - output_folder = Path(self.output_folder) # Retrieve the stored + output_folder = Path(self.output_folder) # Retrieve the stored # Only collect the GUIOutputs.csv file # can add more files if needed outputs = [] From e61d1ecefa747eb37f141403999c1c856f1b5753 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:08:11 -0500 Subject: [PATCH 14/15] address comments --- examples/1App_GCMat/watts_exec.py | 73 +++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/examples/1App_GCMat/watts_exec.py b/examples/1App_GCMat/watts_exec.py index 3d9a8bdf..e8072789 100644 --- a/examples/1App_GCMat/watts_exec.py +++ b/examples/1App_GCMat/watts_exec.py @@ -2,9 +2,18 @@ # SPDX-License-Identifier: MIT """ -This example demonstrates how to use WATTS to run an -GCMAT calculation. This example uses a nuclear -scenario. +This example demonstrates how to use WATTS to run a series of GCMAT calculations for a nuclear scenario. + +In this example, a set of GCMAT simulations are performed, each with a different `end_year` parameter. +The `end_year` parameter specifies the final year of the simulation, allowing us to explore how +extending the simulation period impacts the output. Additionally, we specify different `output_folder` +names for each simulation to organize the results separately. + +By running multiple simulations with varying `end_year` values, this example demonstrates the sensitivity +of the GCMAT model to changes in the simulation period and the resulting effects on key output metrics. + +Note that the `end_year` parameter's unit is in week, and starting from the year is 2010, so the end_year +2028 is equivalent to YEAR 2049, and 2080 is equivalent to YEAR 2050. """ import watts @@ -15,25 +24,38 @@ params = watts.Parameters() template_name = 'gcmat_template.txt' +############################################################################### +# Example of Uranium demand from 2025 to 2035 the original values are from the GCMAT example +# The final demand is the sum of the demand from China, US, Europe, and the rest of the world +# unit in tonnes + final_demand_org = {'2025': 129942.80467537, '2026': 131242.232722123, '2027': 132554.655049345, '2028': 133880.201599838, '2029': 135219.003615836, '2030': 136571.193651995, '2031': 137936.905588515, '2032': 139316.2746444, '2033': 140709.437390844, '2034': 142116.531764752, '2035': 143537.6970824} +# Below are the shares of the demand from China, US, Europe, and the rest of the world china_shares = {'2025': 0.1488, '2026': 0.1509, '2027': 0.153, '2028': 0.1551, '2029': 0.1572, '2030': 0.1593, '2031': 0.1614, '2032': 0.1635, '2033': 0.1656, '2034': 0.1677, '2035': 0.1698} us_shares = {'2025': 0.167705168506287, '2026': 0.16583640931287, '2027': 0.164092357127913, '2028': 0.162454545105463, '2029': 0.160909323261296, '2030': 0.159448692009461, '2031': 0.158069206226392, '2032': 0.156774330726817, '2033': 0.155566621066295, '2034': 0.154449705570859, '2035': 0.153426207060785} europe_shares = {'2025': 0.121418171095092, '2026': 0.119622503016436, '2027': 0.117931399291808, '2028': 0.116333503569193, '2029': 0.11482130552861, '2030': 0.113390051603732, '2031': 0.112036657179276, '2032': 0.110761596604602, '2033': 0.109563494760443, '2034': 0.108442445566371, '2035': 0.107398402556524} row_shares = {'2025': 0.562076660398621, '2026': 0.563641087670695, '2027': 0.56497624358028, '2028': 0.566111951325344, '2029': 0.567069371210094, '2030': 0.567861256386808, '2031': 0.622751279451474, '2032': 0.625765072971703, '2033': 0.622194919609123, '2034': 0.622673325674434, '2035': 0.622981308570158} +############################################################################### +# Example of the new US Uranium demand from 2025 to 2035, these values can be calculated from DYMOND or other sources + us_new_demands = {'2025': 293500, '2026': 292100, '2027': 312300, '2028': 313400, '2029': 377000, '2030': 399100, '2031': 314900, '2032': 361100, '2033': 340200, '2034': 337200, '2035': 336800} -# example from 2025 to 2035 +# As we are changing the US demand, we need to recalculate the shares for all the regions for i in range(2025, 2036): china_demand = final_demand_org[str(i)] * china_shares[str(i)] - us_demand = final_demand_org[str(i)] * us_shares[str(i)] europe_demand = final_demand_org[str(i)] * europe_shares[str(i)] row_demand = final_demand_org[str(i)] * row_shares[str(i)] + # Original US demand, the demand is calculated based on the shares + # Not used in the calculation, here for reference + us_demand = final_demand_org[str(i)] * us_shares[str(i)] + # New US demand us_new_demand = us_new_demands[str(i)] + # New final demand new_final_demand = china_demand + europe_demand + row_demand + us_new_demand params[f'final_demand_{i}'] = new_final_demand params[f'china_{i}'] = china_demand/new_final_demand - params[f'us_{i}'] = us_demand/new_final_demand + params[f'us_{i}'] = us_new_demand/new_final_demand params[f'eu_{i}'] = europe_demand/new_final_demand params[f'row_{i}'] = row_demand/new_final_demand @@ -44,28 +66,43 @@ # Set the default path for the database watts.Database.set_default_path(results_path) print('results_path',results_path) -# Define some example parameter variations -end_years = [2040,2080] -output_folders = [f"output_{year}" for year in end_years] +# Define simulation parameters for multiple runs +# The parameter `end_year` is specified in weeks since the start of the simulation in 2010. +# For example: +# - 1040 weeks corresponds to the year 2030 +# - 1274 weeks corresponds to the year 2040 +# - 2080 weeks corresponds to the year 2050 + +output_years = [2030, 2040, 2050] # Target years for the end of each simulation +# Convert each target year to the corresponding number of weeks since 2010 +end_years = [int((year - 2010) * 52) for year in output_years] +# Generate output folder names based on target years +output_folders = [f"output_{year}" for year in output_years] -# Start timing the simulation +# Start timing the simulation for performance measurement start = time.perf_counter() -# Loop through the variations and run the simulations -for end_year, output_folder in zip(end_years, output_folders): +# Loop through the defined variations in end_years and output_folders to run simulations +for output_year, end_year, output_folder in zip(output_years, end_years, output_folders): + # Update parameters for the current simulation run params['end_year'] = end_year params['output_folder'] = output_folder params['DATABASE_NAME'] = f'GCMAT_{end_year}.db' + + # Display the current parameter settings for transparency and debugging params.show_summary(show_metadata=True, sort_by='key') - # Create the GCMAT plugin - gcmat_plugin = watts.PluginGCMAT('gcmat_template.txt',show_stdout=True, show_stderr=True) - # Run the simulation + # Create the GCMAT plugin instance with the specified template file + gcmat_plugin = watts.PluginGCMAT('gcmat_template.txt', show_stdout=True, show_stderr=True) + + # Run the GCMAT simulation with the current set of parameters gcmat_result = gcmat_plugin(params, end_year=params['end_year'], output_folder=params['output_folder']) - # print the end year price of the gcmat_result.csv_data - print(f'end year {end_year} price: {gcmat_result.csv_data["U buyer price US"].iloc[-1]}') + + # Print the U buyer price in the US for the specified output year from the end of the simulation results + print(f'Output year {output_year} price: {gcmat_result.csv_data["U buyer price US"].iloc[-1]}') # End timing the simulation end = time.perf_counter() -print(f'TOTAL SIMULATION TIME: {np.round((end - start) / 60, 2)} minutes') +# Output the total simulation time for all runs +print(f'TOTAL SIMULATION TIME: {np.round((end - start) / 60, 2)} minutes') \ No newline at end of file From bb07a62968950d95f8a6a2f5c6b52048c9968443 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:16:15 -0500 Subject: [PATCH 15/15] address comments for readme --- examples/1App_GCMat/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/1App_GCMat/README.md b/examples/1App_GCMat/README.md index c98b5708..eb37e887 100644 --- a/examples/1App_GCMat/README.md +++ b/examples/1App_GCMat/README.md @@ -2,7 +2,7 @@ ## Purpose -This example provides a demonstration for using WATTS explore supply chain dynamics and uncertainty under nuclear scenarios of Uranium fuel demand growth or shrinkage, supply disruptions. +This example provides a demonstration for using WATTS to explore supply chain dynamics and uncertainty with GCMat under nuclear scenarios of Uranium fuel demand growth or shrinkage, supply disruptions. ## Code(s)