-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #114 from JiaZhou-PU/gcmat
GCMat plugin - thanks @JiaZhou-PU
- Loading branch information
Showing
8 changed files
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# 1App_GCMat | ||
|
||
## Purpose | ||
|
||
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) | ||
|
||
- GCMat | ||
- Java (GCMat dependency) | ||
- Repast Simphony agent-based modeling toolkit | ||
|
||
## Keywords | ||
|
||
- Rare Earths Supply Chain | ||
- Agent Based Modeling | ||
- 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. | ||
- [__gcmat_template__](gcmat_template.txt): Templated GCMat model for the Uranium demand of nuclear scenarios. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}} {{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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# SPDX-FileCopyrightText: 2022-2023 UChicago Argonne, LLC | ||
# SPDX-License-Identifier: MIT | ||
|
||
""" | ||
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 | ||
from pathlib import Path | ||
import numpy as np | ||
import time | ||
|
||
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} | ||
|
||
# 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)] | ||
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_new_demand/new_final_demand | ||
params[f'eu_{i}'] = europe_demand/new_final_demand | ||
params[f'row_{i}'] = row_demand/new_final_demand | ||
|
||
# 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 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 for performance measurement | ||
start = time.perf_counter() | ||
|
||
# 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 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 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() | ||
|
||
# Output the total simulation time for all runs | ||
print(f'TOTAL SIMULATION TIME: {np.round((end - start) / 60, 2)} minutes') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
# SPDX-FileCopyrightText: 2024 UChicago Argonne, LLC | ||
# SPDX-License-Identifier: MIT | ||
|
||
from pathlib import Path | ||
import shutil | ||
import subprocess | ||
from typing import List, Optional | ||
import os | ||
import pandas as pd | ||
|
||
from .plugin import Plugin | ||
from .results import Results, ExecInfo | ||
from .fileutils import PathLike | ||
from .parameters import Parameters | ||
from .template import TemplateRenderer | ||
|
||
|
||
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) | ||
|
||
# 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) | ||
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 | ||
""" | ||
# 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 | ||
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 | ||
# 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) |