From 904d44fdbbef80b5d638a9cc214f53df74f56646 Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Sun, 25 Aug 2024 16:08:31 -0600 Subject: [PATCH 01/15] New script for WACCM extract. Command-line args only. --- tools/waccmToMusicBox.py | 137 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 tools/waccmToMusicBox.py diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py new file mode 100644 index 00000000..6e3ddc18 --- /dev/null +++ b/tools/waccmToMusicBox.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# waccmToMusicBox.py +# MusicBox: Extract variables from WACCM model output, +# and convert to initial conditions for MusicBox (case TS1). +# +# Author: Carl Drews +# Copyright 2024 by Atomospheric Chemistry Observations & Modeling (UCAR/ACOM) + +#import os +import argparse +#import numpy +import datetime +import xarray +import sys + +import logging +logger = logging.getLogger(__name__) + + + +# configure argparse for key-value pairs +class KeyValueAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + for value in values: + key, val = value.split('=') + setattr(namespace, key, val) + +# Retrieve named arguments from the command line and +# return in a dictionary of keywords. +# argPairs = list of arguments, probably from sys.argv[1:] +# named arguments are formatted like this=3.14159 +# return dictionary of keywords and values +def getArgsDictionary(argPairs): + parser = argparse.ArgumentParser( + description='Process some key=value pairs.') + parser.add_argument( + 'key_value_pairs', + nargs='+', # This means one or more arguments are expected + action=KeyValueAction, + help="Arguments in key=value format. Example: configFile=my_config.json" + ) + + argDict = vars(parser.parse_args(argPairs)) # return dictionary + + return (argDict) + + + +# Convert safely from string to integer (alphas convert to 0). +def safeInt(intString): + intValue = 0 + try: + intValue = int(intString) + except ValueError as error: + intValue = 0 + + return intValue + + + +# Convert string to number, or 0.0 if not numeric. +# numString = string that probably can be converted +def safeFloat(numString): + result = -1.0 + try: + result = float(numString) + except ValueError: + result = 0.0 + + return result + + + +# Build and return dictionary of WACCM variable names +# and their MusicBox equivalents. +def getMusicaDictionary(): + varMap = { + "Nairobi": [-1.2921, 36.8219], + "Kampala": [0.3476, 32.5825], + "Kigali": [-1.9441, 30.0619], + "Dodoma": [-6.1630, 35.7516], + "Lilongwe": [-13.9626, 33.7741], + "Lusaka": [-15.3875, 28.3228] + } + + return(dict(sorted(varMap.items()))) + + + +# Main routine begins here. +def main(): + logging.basicConfig(stream=sys.stdout, level=logging.INFO) + logger.info("{}".format(__file__)) + logger.info("Start time: {}".format(datetime.datetime.now())) + + # retrieve and parse the command-line arguments + myArgs = getArgsDictionary(sys.argv[1:]) + logger.info("Command line = {}".format(myArgs)) + + # set up the directories + waccmDir = None + if ("waccmDir" in myArgs): + waccmDir = myArgs.get("waccmDir") + + musicaDir = None + if ("musicaDir" in myArgs): + musicaDir = myArgs.get("musicaDir") + + # get the geographical location to retrieve + lat = None + if ("latitude" in myArgs): + lat = safeFloat(myArgs.get("latitude")) + + lon = None + if ("longitude" in myArgs): + lon = safeFloat(myArgs.get("longitude")) + + logger.info("Retrieve WACCM conditions at ({} North, {} East)." + .format(lat, lon)) + + logger.info("End time: {}".format(datetime.datetime.now())) + sys.exit(0) # no error + + + +if (__name__ == "__main__"): + main() + + + logger.info("End time: {}".format(datetime.datetime.now())) + sys.exit(0) # no error + + + +if (__name__ == "__main__"): + main() + From 529bb186e2e68a7927b0d21c4b64c363fa213c1b Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Sun, 25 Aug 2024 20:24:12 -0600 Subject: [PATCH 02/15] Opening the WACCM file with xarray. --- tools/waccmToMusicBox.py | 64 +++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index 6e3ddc18..175109b6 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -75,18 +75,46 @@ def safeFloat(numString): # and their MusicBox equivalents. def getMusicaDictionary(): varMap = { - "Nairobi": [-1.2921, 36.8219], - "Kampala": [0.3476, 32.5825], - "Kigali": [-1.9441, 30.0619], - "Dodoma": [-6.1630, 35.7516], - "Lilongwe": [-13.9626, 33.7741], - "Lusaka": [-15.3875, 28.3228] + "H2O": "H2O", + "TEPOMUC": "jtepo", + "BENZENE": "jbenzene", + "O3": "O3", + "NH3": "NH3", + "CH4": "CH4", + "O": "O" } return(dict(sorted(varMap.items()))) +# Read array values at a single lat-lon-time point. +# waccmMusicaDict = mapping from WACCM names to MusicBox +# latitude, longitude = geo-coordinates of retrieval point +# when = date and time to extract +# modelDir = directory containing model output +# return xarray of variable names and values +def readWACCM(waccmMusicaDict, latitude, longitude, + when, modelDir): + + # create the filename + waccmFilename = ("f.e22.beta02.FWSD.f09_f09_mg17.cesm2_2_beta02.forecast.001.cam.h3.{:4d}-{:02d}-{:02}-00000.nc" + .format(when.year, when.month, when.day)) + logger.info("WACCM file = {}".format(waccmFilename)) + + # open dataset for reading + waccmDataSet = xarray.open_dataset("{}/{}".format(modelDir, waccmFilename)) + if (True): + # diagnostic to look at dataset structure + logger.info("WACCM dataset = {}".format(waccmDataSet)) + + # close the NetCDF file + waccmDataSet.close() + + return(None) + + + # Main routine begins here. def main(): logging.basicConfig(stream=sys.stdout, level=logging.INFO) @@ -106,6 +134,15 @@ def main(): if ("musicaDir" in myArgs): musicaDir = myArgs.get("musicaDir") + # get the date-time to retrieve + dateStr = None + if ("date" in myArgs): + dateStr = myArgs.get("date") + + timeStr = "00:00" + if ("time" in myArgs): + timeStr = myArgs.get("time") + # get the geographical location to retrieve lat = None if ("latitude" in myArgs): @@ -115,8 +152,19 @@ def main(): if ("longitude" in myArgs): lon = safeFloat(myArgs.get("longitude")) - logger.info("Retrieve WACCM conditions at ({} North, {} East)." - .format(lat, lon)) + retrieveWhen = datetime.datetime.strptime( + "{} {}".format(dateStr, timeStr), "%Y%m%d %H:%M") + + logger.info("Retrieve WACCM conditions at ({} North, {} East) when {}." + .format(lat, lon, retrieveWhen)) + + # Read named variables from WACCM model output. + varValues = readWACCM(getMusicaDictionary(), + lat, lon, retrieveWhen, waccmDir) + + # Perform any conversions needed, or derive variables. + + # Write CSV file for MusicBox initial conditions. logger.info("End time: {}".format(datetime.datetime.now())) sys.exit(0) # no error From b1df7cd36913d15155abff2f7b0cc40a4ad3e052 Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Sun, 25 Aug 2024 21:54:26 -0600 Subject: [PATCH 03/15] Retrieving WACCM values at requested location and time. --- tools/waccmToMusicBox.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index 175109b6..0a77e630 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -81,7 +81,7 @@ def getMusicaDictionary(): "O3": "O3", "NH3": "NH3", "CH4": "CH4", - "O": "O" + "O": "O" # test var not in WACCM } return(dict(sorted(varMap.items()))) @@ -93,7 +93,7 @@ def getMusicaDictionary(): # latitude, longitude = geo-coordinates of retrieval point # when = date and time to extract # modelDir = directory containing model output -# return xarray of variable names and values +# return dictionary of MUSICA variable names, values, and units def readWACCM(waccmMusicaDict, latitude, longitude, when, modelDir): @@ -108,10 +108,37 @@ def readWACCM(waccmMusicaDict, latitude, longitude, # diagnostic to look at dataset structure logger.info("WACCM dataset = {}".format(waccmDataSet)) + # retrieve all vars at a single point + whenStr = when.strftime("%Y-%m-%d %H:%M:%S") + logger.info("whenStr = {}".format(whenStr)) + singlePoint = waccmDataSet.sel(lon=longitude, lat=latitude, lev=1000.0, + time=whenStr, method="nearest") + if (True): + # diagnostic to look at single point structure + logger.info("WACCM singlePoint = {}".format(singlePoint)) + + # loop through vars and build another dictionary + musicaDict = {} + for waccmKey, musicaName in waccmMusicaDict.items(): + logger.info("WACCM Chemical = {}".format(waccmKey)) + if not waccmKey in singlePoint: + logger.warning("Requested variable {} not found in WACCM model output." + .format(waccmKey)) + musicaTuple = (waccmKey, None, None) + musicaDict[musicaName] = musicaTuple + continue + + chemSinglePoint = singlePoint[waccmKey] + if (True): + logger.info("{} = {}".format(waccmKey, chemSinglePoint)) + logger.info("{} = {} {}".format(waccmKey, chemSinglePoint.values, chemSinglePoint.units)) + musicaTuple = (waccmKey, chemSinglePoint.values, chemSinglePoint.units) + musicaDict[musicaName] = musicaTuple + # close the NetCDF file waccmDataSet.close() - return(None) + return(musicaDict) @@ -161,6 +188,7 @@ def main(): # Read named variables from WACCM model output. varValues = readWACCM(getMusicaDictionary(), lat, lon, retrieveWhen, waccmDir) + logger.info("WACCM varValues = {}".format(varValues)) # Perform any conversions needed, or derive variables. From 595001dbfd21085cfc89ae3764e2056b5464e8e2 Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Sun, 25 Aug 2024 22:11:26 -0600 Subject: [PATCH 04/15] Added example conversion. --- tools/waccmToMusicBox.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index 0a77e630..a9b5598a 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -76,12 +76,12 @@ def safeFloat(numString): def getMusicaDictionary(): varMap = { "H2O": "H2O", - "TEPOMUC": "jtepo", + "TEPOMUC": "jtepo", # test var not in WACCM "BENZENE": "jbenzene", "O3": "O3", "NH3": "NH3", "CH4": "CH4", - "O": "O" # test var not in WACCM + "O": "O" # test var not in WACCM } return(dict(sorted(varMap.items()))) @@ -142,6 +142,21 @@ def readWACCM(waccmMusicaDict, latitude, longitude, +# Perform any numeric conversion needed. +# varDict = originally read from WACCM, tuples are (musicaName, value, units) +# return varDict with values modified +def convertWaccm(varDict): + # set up indexes for the tuple + musicaIndex = 0 + valueIndex = 1 + unitIndex = 2 + + nh3Tuple = varDict["NH3"] + varDict["NH3"] = (nh3Tuple[0], nh3Tuple[valueIndex] * 1000, "milli" + nh3Tuple[2]) + return(varDict) + + + # Main routine begins here. def main(): logging.basicConfig(stream=sys.stdout, level=logging.INFO) @@ -188,9 +203,11 @@ def main(): # Read named variables from WACCM model output. varValues = readWACCM(getMusicaDictionary(), lat, lon, retrieveWhen, waccmDir) - logger.info("WACCM varValues = {}".format(varValues)) + logger.info("Original WACCM varValues = {}".format(varValues)) # Perform any conversions needed, or derive variables. + convertWaccm(varValues) + logger.info("Converted WACCM varValues = {}".format(varValues)) # Write CSV file for MusicBox initial conditions. From a556e70248350ca6a3a1397100e10d67ab647d8d Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Sun, 25 Aug 2024 22:49:55 -0600 Subject: [PATCH 05/15] Writing initial values to CSV file. --- tools/waccmToMusicBox.py | 51 +++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index a9b5598a..a9957f7c 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -142,18 +142,51 @@ def readWACCM(waccmMusicaDict, latitude, longitude, +# set up indexes for the tuple +musicaIndex = 0 +valueIndex = 1 +unitIndex = 2 + # Perform any numeric conversion needed. # varDict = originally read from WACCM, tuples are (musicaName, value, units) # return varDict with values modified def convertWaccm(varDict): - # set up indexes for the tuple - musicaIndex = 0 - valueIndex = 1 - unitIndex = 2 + # convert Ammonia to milli-moles + nh3Tuple = varDict["NH3"] + varDict["NH3"] = (nh3Tuple[0], nh3Tuple[valueIndex] * 1000, "milli" + nh3Tuple[2]) + return(varDict) + + + +# Write CSV file suitable for initial_conditions.csv in MusicBox +# initValues = dictionary of Musica varnames and (WACCM name, value, units) +def writeInitCSV(initValues, filename): + fp = open(filename, "w") + + # write the column titles + firstColumn = True + for key, value in initValues.items(): + if (firstColumn): + firstColumn = False + else: + fp.write(",") + + fp.write(key) + fp.write("\n") + + # write the variable values + firstColumn = True + for key, value in initValues.items(): + if (firstColumn): + firstColumn = False + else: + fp.write(",") + + fp.write("{}".format(value[valueIndex])) + fp.write("\n") - nh3Tuple = varDict["NH3"] - varDict["NH3"] = (nh3Tuple[0], nh3Tuple[valueIndex] * 1000, "milli" + nh3Tuple[2]) - return(varDict) + fp.close() + return @@ -206,10 +239,12 @@ def main(): logger.info("Original WACCM varValues = {}".format(varValues)) # Perform any conversions needed, or derive variables. - convertWaccm(varValues) + varValues = convertWaccm(varValues) logger.info("Converted WACCM varValues = {}".format(varValues)) # Write CSV file for MusicBox initial conditions. + csvName = "{}/{}".format(musicaDir, "initial_conditions.csv") + writeInitCSV(varValues, csvName) logger.info("End time: {}".format(datetime.datetime.now())) sys.exit(0) # no error From 3308858235295adcff48c131337c29635d80b2e5 Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Mon, 26 Aug 2024 13:56:05 -0600 Subject: [PATCH 06/15] Added temperature and pressure. --- tools/waccmToMusicBox.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index a9957f7c..da72a34b 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -75,6 +75,8 @@ def safeFloat(numString): # and their MusicBox equivalents. def getMusicaDictionary(): varMap = { + "T": "temperature", + "PS": "pressure", "H2O": "H2O", "TEPOMUC": "jtepo", # test var not in WACCM "BENZENE": "jbenzene", From c3c9e641de685137133b3dafdc92b1ee33c321fb Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Mon, 26 Aug 2024 14:08:59 -0600 Subject: [PATCH 07/15] Stub function for writing JSON output. --- tools/waccmToMusicBox.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index da72a34b..3b3c5044 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -160,7 +160,7 @@ def convertWaccm(varDict): -# Write CSV file suitable for initial_conditions.csv in MusicBox +# Write CSV file suitable for initial_conditions.csv in MusicBox. # initValues = dictionary of Musica varnames and (WACCM name, value, units) def writeInitCSV(initValues, filename): fp = open(filename, "w") @@ -192,6 +192,18 @@ def writeInitCSV(initValues, filename): +# Write JSON fragment suitable for my_config.json in MusicBox. +# initValues = dictionary of Musica varnames and (WACCM name, value, units) +def writeInitJSON(initValues, filename): + fp = open(filename, "w") + + fp.write("Hello, JSON World!\n") + + fp.close() + return + + + # Main routine begins here. def main(): logging.basicConfig(stream=sys.stdout, level=logging.INFO) @@ -244,9 +256,15 @@ def main(): varValues = convertWaccm(varValues) logger.info("Converted WACCM varValues = {}".format(varValues)) - # Write CSV file for MusicBox initial conditions. - csvName = "{}/{}".format(musicaDir, "initial_conditions.csv") - writeInitCSV(varValues, csvName) + if (False): + # Write CSV file for MusicBox initial conditions. + csvName = "{}/{}".format(musicaDir, "initial_conditions.csv") + writeInitCSV(varValues, csvName) + + else: + # Write JSON file for MusicBox initial conditions. + jsonName = "{}/{}".format(musicaDir, "initial_config.json") + writeInitJSON(varValues, jsonName) logger.info("End time: {}".format(datetime.datetime.now())) sys.exit(0) # no error From ed34d79874a362073d4d48c5125ef1cb9f7a8474 Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Mon, 26 Aug 2024 15:53:16 -0600 Subject: [PATCH 08/15] Writing initial concentrations to JSON file. --- tools/waccmToMusicBox.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index 3b3c5044..196b8fc5 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -8,9 +8,9 @@ #import os import argparse -#import numpy import datetime import xarray +import json import sys import logging @@ -132,9 +132,9 @@ def readWACCM(waccmMusicaDict, latitude, longitude, chemSinglePoint = singlePoint[waccmKey] if (True): - logger.info("{} = {}".format(waccmKey, chemSinglePoint)) - logger.info("{} = {} {}".format(waccmKey, chemSinglePoint.values, chemSinglePoint.units)) - musicaTuple = (waccmKey, chemSinglePoint.values, chemSinglePoint.units) + logger.info("{} = object {}".format(waccmKey, chemSinglePoint)) + logger.info("{} = value {} {}".format(waccmKey, chemSinglePoint.values, chemSinglePoint.units)) + musicaTuple = (waccmKey, float(chemSinglePoint.values.mean()), chemSinglePoint.units) musicaDict[musicaName] = musicaTuple # close the NetCDF file @@ -195,11 +195,22 @@ def writeInitCSV(initValues, filename): # Write JSON fragment suitable for my_config.json in MusicBox. # initValues = dictionary of Musica varnames and (WACCM name, value, units) def writeInitJSON(initValues, filename): - fp = open(filename, "w") - fp.write("Hello, JSON World!\n") + # set up dictionary of vars and initial values + dictName = "chemical species" + initConfig = {dictName: {} } - fp.close() + for key, value in initValues.items(): + initConfig[dictName][key] = { + "initial value [{}]".format(value[unitIndex]): value[valueIndex]} + + # write JSON content to the file + fpJson = open(filename, "w") + + json.dump(initConfig, fpJson, indent=2) + fpJson.close() + + fpJson.close() return From 717c079c248e20f8065b79ad2917265afe5664ac Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Mon, 26 Aug 2024 16:06:58 -0600 Subject: [PATCH 09/15] Comment for 0-dim array. --- tools/waccmToMusicBox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index 196b8fc5..f7ab5867 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -134,7 +134,7 @@ def readWACCM(waccmMusicaDict, latitude, longitude, if (True): logger.info("{} = object {}".format(waccmKey, chemSinglePoint)) logger.info("{} = value {} {}".format(waccmKey, chemSinglePoint.values, chemSinglePoint.units)) - musicaTuple = (waccmKey, float(chemSinglePoint.values.mean()), chemSinglePoint.units) + musicaTuple = (waccmKey, float(chemSinglePoint.values.mean()), chemSinglePoint.units) # from 0-dim array musicaDict[musicaName] = musicaTuple # close the NetCDF file From cecd5bcb1f0bd6783e07e855f9ae2aa3e0e4173b Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Mon, 26 Aug 2024 17:05:53 -0600 Subject: [PATCH 10/15] More realistic example species. --- tools/waccmToMusicBox.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index f7ab5867..7868c85d 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -77,13 +77,11 @@ def getMusicaDictionary(): varMap = { "T": "temperature", "PS": "pressure", - "H2O": "H2O", - "TEPOMUC": "jtepo", # test var not in WACCM - "BENZENE": "jbenzene", + "N2O": "N2O", + "H2O2": "H2O2", "O3": "O3", "NH3": "NH3", - "CH4": "CH4", - "O": "O" # test var not in WACCM + "CH4": "CH4" } return(dict(sorted(varMap.items()))) @@ -155,7 +153,8 @@ def readWACCM(waccmMusicaDict, latitude, longitude, def convertWaccm(varDict): # convert Ammonia to milli-moles nh3Tuple = varDict["NH3"] - varDict["NH3"] = (nh3Tuple[0], nh3Tuple[valueIndex] * 1000, "milli" + nh3Tuple[2]) + varDict["NH3"] = (nh3Tuple[0], nh3Tuple[valueIndex] * 1000, "milli" + nh3Tuple[unitIndex]) + return(varDict) From b032ba8b639f05da9ae5aecd5ad9c16018ff664e Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Tue, 27 Aug 2024 15:15:51 -0600 Subject: [PATCH 11/15] Copy template directory; no insertion or compression yet. --- tools/waccmToMusicBox.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index 7868c85d..5f4173ab 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -12,6 +12,8 @@ import xarray import json import sys +import os +import shutil import logging logger = logging.getLogger(__name__) @@ -214,6 +216,28 @@ def writeInitJSON(initValues, filename): +# Reproduce the MusicBox configuration with new initial values. +# initValues = dictionary of Musica varnames and (WACCM name, value, units) +# templateDir = directory containing configuration files and camp_data +# destDir = the template will be created in this directory +def insertIntoTemplate(initValues, templateDir, destDir): + + # copy the template directory to working area + destPath = os.path.basename(os.path.normpath(templateDir)) + destPath = os.path.join(destDir, destPath) + logger.info("Create new configuration in = {}".format(destPath)) + + # remove directory if it already exists + if os.path.exists(destPath): + shutil.rmtree(destPath) + + # copy the template directory + shutil.copytree(templateDir, destPath) + + return + + + # Main routine begins here. def main(): logging.basicConfig(stream=sys.stdout, level=logging.INFO) @@ -254,6 +278,10 @@ def main(): retrieveWhen = datetime.datetime.strptime( "{} {}".format(dateStr, timeStr), "%Y%m%d %H:%M") + template = None + if ("template" in myArgs): + template = myArgs.get("template") + logger.info("Retrieve WACCM conditions at ({} North, {} East) when {}." .format(lat, lon, retrieveWhen)) @@ -266,16 +294,20 @@ def main(): varValues = convertWaccm(varValues) logger.info("Converted WACCM varValues = {}".format(varValues)) - if (False): + if (True): # Write CSV file for MusicBox initial conditions. csvName = "{}/{}".format(musicaDir, "initial_conditions.csv") writeInitCSV(varValues, csvName) - else: + if (True): # Write JSON file for MusicBox initial conditions. jsonName = "{}/{}".format(musicaDir, "initial_config.json") writeInitJSON(varValues, jsonName) + if (True and template is not None): + logger.info("Insert values into template {}".format(template)) + insertIntoTemplate(varValues, template, musicaDir) + logger.info("End time: {}".format(datetime.datetime.now())) sys.exit(0) # no error From aca12fcdd9bed9f70970d2e2fef6147aa136b6c3 Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Tue, 27 Aug 2024 16:58:25 -0600 Subject: [PATCH 12/15] Inserted chemical concentrations into the JSON. --- tools/waccmToMusicBox.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index 5f4173ab..8415127c 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -234,6 +234,44 @@ def insertIntoTemplate(initValues, templateDir, destDir): # copy the template directory shutil.copytree(templateDir, destPath) + # find the standard configuration file and parse it + myConfigFile = os.path.join(destPath, "my_config.json") + with open(myConfigFile) as jsonFile: + myConfig = json.load(jsonFile) + + # locate the section for chemical concentrations + chemSpeciesTag = "chemical species" + chemSpecies = myConfig[chemSpeciesTag] + logger.info("chemSpecies = {}".format(chemSpecies)) + del myConfig[chemSpeciesTag] # delete the existing section + + # set up dictionary of chemicals and initial values + chemValueDict = {} + temperature = 0.0 + pressure = 0.0 + for key, value in initValues.items(): + if (key == "temperature"): + temperature = safeFloat(value[valueIndex]) + continue + if (key == "pressure"): + pressure = safeFloat(value[valueIndex]) + continue + + chemValueDict[key] = { + "initial value [{}]".format(value[unitIndex]): value[valueIndex]} + + myConfig[chemSpeciesTag] = chemValueDict + + # replace the values of temperature and pressure + envConditionsTag = "environmental conditions" + envConfig = myConfig[envConditionsTag] + envConfig["temperature"]["initial value [K]"] = temperature + envConfig["pressure"]["initial value [Pa]"] = pressure + + # save over the former json file + with open(myConfigFile, "w") as myConfigFile: + json.dump(myConfig, myConfigFile, indent=2) + return From 2e8722aaf699d7e13c34d4075f299b4eb93188be Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Tue, 27 Aug 2024 17:30:44 -0600 Subject: [PATCH 13/15] Added unit conversion from mol/mol to mol m-3. --- tools/waccmToMusicBox.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index 8415127c..9b6d9b75 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -153,9 +153,14 @@ def readWACCM(waccmMusicaDict, latitude, longitude, # varDict = originally read from WACCM, tuples are (musicaName, value, units) # return varDict with values modified def convertWaccm(varDict): - # convert Ammonia to milli-moles - nh3Tuple = varDict["NH3"] - varDict["NH3"] = (nh3Tuple[0], nh3Tuple[valueIndex] * 1000, "milli" + nh3Tuple[unitIndex]) + + moleToM3 = 1000 / 22.4 + + for key, vTuple in varDict.items(): + # convert moles / mole to moles / cubic meter + units = vTuple[unitIndex] + if (units == "mol/mol"): + varDict[key] = (vTuple[0], vTuple[valueIndex] * moleToM3, "mol m-3") return(varDict) @@ -242,7 +247,7 @@ def insertIntoTemplate(initValues, templateDir, destDir): # locate the section for chemical concentrations chemSpeciesTag = "chemical species" chemSpecies = myConfig[chemSpeciesTag] - logger.info("chemSpecies = {}".format(chemSpecies)) + logger.info("Replace chemSpecies = {}".format(chemSpecies)) del myConfig[chemSpeciesTag] # delete the existing section # set up dictionary of chemicals and initial values From b5204f88ce1f3481d2402a17f229880079810eae Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Tue, 27 Aug 2024 20:13:36 -0600 Subject: [PATCH 14/15] Compressed into zip file, still need to copy into TS1 dir. --- tools/waccmToMusicBox.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index 9b6d9b75..fed11e8d 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -228,8 +228,8 @@ def writeInitJSON(initValues, filename): def insertIntoTemplate(initValues, templateDir, destDir): # copy the template directory to working area - destPath = os.path.basename(os.path.normpath(templateDir)) - destPath = os.path.join(destDir, destPath) + destZip = os.path.basename(os.path.normpath(templateDir)) + destPath = os.path.join(destDir, destZip) logger.info("Create new configuration in = {}".format(destPath)) # remove directory if it already exists @@ -274,8 +274,14 @@ def insertIntoTemplate(initValues, templateDir, destDir): envConfig["pressure"]["initial value [Pa]"] = pressure # save over the former json file - with open(myConfigFile, "w") as myConfigFile: - json.dump(myConfig, myConfigFile, indent=2) + with open(myConfigFile, "w") as myConfigFp: + json.dump(myConfig, myConfigFp, indent=2) + + # compress the written directory as a zip file + shutil.make_archive(destPath, "zip", + root_dir=destDir, base_dir=destZip) + + # move into the created directory return From 6c25d00564609c7e3d93926082efef6cdd830544 Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Tue, 27 Aug 2024 20:28:19 -0600 Subject: [PATCH 15/15] Copy zip archive to example directory. --- tools/waccmToMusicBox.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py index fed11e8d..8fadcd23 100644 --- a/tools/waccmToMusicBox.py +++ b/tools/waccmToMusicBox.py @@ -282,6 +282,7 @@ def insertIntoTemplate(initValues, templateDir, destDir): root_dir=destDir, base_dir=destZip) # move into the created directory + shutil.move(destPath + ".zip", destPath) return