From 904d44fdbbef80b5d638a9cc214f53df74f56646 Mon Sep 17 00:00:00 2001 From: Carl Drews Date: Sun, 25 Aug 2024 16:08:31 -0600 Subject: [PATCH 01/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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 8c2efc251088e53acb6a0818f7312c9a3b59bef5 Mon Sep 17 00:00:00 2001 From: Kyle Shores Date: Mon, 26 Aug 2024 21:44:10 -0500 Subject: [PATCH 11/12] creating exectuable --- pyproject.toml | 4 +++- src/acom_music_box/tools/__init__.py | 0 {tools => src/acom_music_box/tools}/waccmToMusicBox.py | 0 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 src/acom_music_box/tools/__init__.py rename {tools => src/acom_music_box/tools}/waccmToMusicBox.py (100%) diff --git a/pyproject.toml b/pyproject.toml index bb75bd54..feabcf26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,8 @@ classifiers = ["License :: OSI Approved :: Apache Software License"] dynamic = ["version", "description"] dependencies = [ - "musica==0.7.3" + "musica==0.7.3", + "xarray", ] [project.urls] @@ -29,3 +30,4 @@ Home = "https://github.com/NCAR/music-box" [project.scripts] music_box = "acom_music_box.main:main" +waccmToMusicBox = "acom_music_box.tools.waccmToMusicBox:main" diff --git a/src/acom_music_box/tools/__init__.py b/src/acom_music_box/tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tools/waccmToMusicBox.py b/src/acom_music_box/tools/waccmToMusicBox.py similarity index 100% rename from tools/waccmToMusicBox.py rename to src/acom_music_box/tools/waccmToMusicBox.py From cd103f927aec0b5e4434d598bf40fbed1e9a9438 Mon Sep 17 00:00:00 2001 From: Kyle Shores Date: Wed, 28 Aug 2024 08:44:21 -0500 Subject: [PATCH 12/12] moving Carl's newest version --- src/acom_music_box/__init__.py | 2 +- src/acom_music_box/tools/waccmToMusicBox.py | 318 ++++++++++------- tools/waccmToMusicBox.py | 367 -------------------- 3 files changed, 196 insertions(+), 491 deletions(-) delete mode 100644 tools/waccmToMusicBox.py diff --git a/src/acom_music_box/__init__.py b/src/acom_music_box/__init__.py index 74f48e62..94bc5a1f 100644 --- a/src/acom_music_box/__init__.py +++ b/src/acom_music_box/__init__.py @@ -4,7 +4,7 @@ This package contains modules for handling various aspects of a music box, including species, products, reactants, reactions, and more. """ -__version__ = "2.3.1" +__version__ = "2.3.2" from .utils import convert_time, convert_pressure, convert_temperature, convert_concentration from .species import Species diff --git a/src/acom_music_box/tools/waccmToMusicBox.py b/src/acom_music_box/tools/waccmToMusicBox.py index 7868c85d..3478e15d 100644 --- a/src/acom_music_box/tools/waccmToMusicBox.py +++ b/src/acom_music_box/tools/waccmToMusicBox.py @@ -6,18 +6,19 @@ # Author: Carl Drews # Copyright 2024 by Atomospheric Chemistry Observations & Modeling (UCAR/ACOM) -#import os +# import os import argparse import datetime import xarray import json import sys +import os +import shutil 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): @@ -30,6 +31,8 @@ def __call__(self, parser, namespace, values, option_string=None): # 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.') @@ -45,47 +48,43 @@ def getArgsDictionary(argPairs): 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 - try: - intValue = int(intString) - except ValueError as error: - intValue = 0 - - return intValue + 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 + 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 = { - "T": "temperature", - "PS": "pressure", - "N2O": "N2O", - "H2O2": "H2O2", - "O3": "O3", - "NH3": "NH3", - "CH4": "CH4" - } - - return(dict(sorted(varMap.items()))) + varMap = { + "T": "temperature", + "PS": "pressure", + "N2O": "N2O", + "H2O2": "H2O2", + "O3": "O3", + "NH3": "NH3", + "CH4": "CH4" + } + return (dict(sorted(varMap.items()))) # Read array values at a single lat-lon-time point. @@ -95,51 +94,50 @@ def getMusicaDictionary(): # modelDir = directory containing model output # return dictionary of MUSICA variable names, values, and units 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)) - - # 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] + 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)) + + # 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): - logger.info("{} = object {}".format(waccmKey, chemSinglePoint)) - logger.info("{} = value {} {}".format(waccmKey, chemSinglePoint.values, chemSinglePoint.units)) - musicaTuple = (waccmKey, float(chemSinglePoint.values.mean()), chemSinglePoint.units) # from 0-dim array - musicaDict[musicaName] = musicaTuple + # diagnostic to look at single point structure + logger.info("WACCM singlePoint = {}".format(singlePoint)) - # close the NetCDF file - waccmDataSet.close() + # loop through vars and build another dictionary + musicaDict = {} + for waccmKey, musicaName in waccmMusicaDict.items(): + logger.info("WACCM Chemical = {}".format(waccmKey)) + if waccmKey not in singlePoint: + logger.warning("Requested variable {} not found in WACCM model output." + .format(waccmKey)) + musicaTuple = (waccmKey, None, None) + musicaDict[musicaName] = musicaTuple + continue - return(musicaDict) + chemSinglePoint = singlePoint[waccmKey] + 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) # from 0-dim array + musicaDict[musicaName] = musicaTuple + # close the NetCDF file + waccmDataSet.close() + + return (musicaDict) # set up indexes for the tuple @@ -150,68 +148,138 @@ 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): - # convert Ammonia to milli-moles - nh3Tuple = varDict["NH3"] - varDict["NH3"] = (nh3Tuple[0], nh3Tuple[valueIndex] * 1000, "milli" + nh3Tuple[unitIndex]) - return(varDict) + 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) # 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 = open(filename, "w") - fp.write(key) - fp.write("\n") + # write the column titles + firstColumn = True + for key, value in initValues.items(): + if (firstColumn): + firstColumn = False + else: + fp.write(",") - # write the variable values - firstColumn = True - for key, value in initValues.items(): - if (firstColumn): - firstColumn = False - else: - fp.write(",") + fp.write(key) + fp.write("\n") - fp.write("{}".format(value[valueIndex])) - fp.write("\n") + # write the variable values + firstColumn = True + for key, value in initValues.items(): + if (firstColumn): + firstColumn = False + else: + fp.write(",") - fp.close() - return + fp.write("{}".format(value[valueIndex])) + fp.write("\n") + fp.close() + return # Write JSON fragment suitable for my_config.json in MusicBox. # initValues = dictionary of Musica varnames and (WACCM name, value, units) def writeInitJSON(initValues, filename): - # set up dictionary of vars and initial values - dictName = "chemical species" - initConfig = {dictName: {} } + # set up dictionary of vars and initial values + dictName = "chemical species" + initConfig = {dictName: {}} + + for key, value in initValues.items(): + initConfig[dictName][key] = { + "initial value [{}]".format(value[unitIndex]): value[valueIndex]} - 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") - # write JSON content to the file - fpJson = open(filename, "w") + json.dump(initConfig, fpJson, indent=2) + fpJson.close() - json.dump(initConfig, fpJson, indent=2) - fpJson.close() + fpJson.close() + return - fpJson.close() - return +# 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 + 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 + if os.path.exists(destPath): + shutil.rmtree(destPath) + + # 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("Replace 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 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 + shutil.move(destPath + ".zip", destPath) + + return # Main routine begins here. @@ -236,11 +304,11 @@ def main(): # get the date-time to retrieve dateStr = None if ("date" in myArgs): - dateStr = myArgs.get("date") + dateStr = myArgs.get("date") timeStr = "00:00" if ("time" in myArgs): - timeStr = myArgs.get("time") + timeStr = myArgs.get("time") # get the geographical location to retrieve lat = None @@ -252,44 +320,48 @@ def main(): lon = safeFloat(myArgs.get("longitude")) retrieveWhen = datetime.datetime.strptime( - "{} {}".format(dateStr, timeStr), "%Y%m%d %H:%M") + "{} {}".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)) + .format(lat, lon, retrieveWhen)) # Read named variables from WACCM model output. varValues = readWACCM(getMusicaDictionary(), - lat, lon, retrieveWhen, waccmDir) + lat, lon, retrieveWhen, waccmDir) logger.info("Original WACCM varValues = {}".format(varValues)) # Perform any conversions needed, or derive variables. varValues = convertWaccm(varValues) logger.info("Converted WACCM varValues = {}".format(varValues)) - if (False): - # Write CSV file for MusicBox initial conditions. - csvName = "{}/{}".format(musicaDir, "initial_conditions.csv") - writeInitCSV(varValues, csvName) + if (True): + # Write CSV file for MusicBox initial conditions. + csvName = "{}/{}".format(musicaDir, "initial_conditions.csv") + writeInitCSV(varValues, csvName) + + if (True): + # Write JSON file for MusicBox initial conditions. + jsonName = "{}/{}".format(musicaDir, "initial_config.json") + writeInitJSON(varValues, jsonName) - else: - # 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 - if (__name__ == "__main__"): main() - logger.info("End time: {}".format(datetime.datetime.now())) sys.exit(0) # no error - if (__name__ == "__main__"): main() - diff --git a/tools/waccmToMusicBox.py b/tools/waccmToMusicBox.py deleted file mode 100644 index 3478e15d..00000000 --- a/tools/waccmToMusicBox.py +++ /dev/null @@ -1,367 +0,0 @@ -#!/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 datetime -import xarray -import json -import sys -import os -import shutil - -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 = { - "T": "temperature", - "PS": "pressure", - "N2O": "N2O", - "H2O2": "H2O2", - "O3": "O3", - "NH3": "NH3", - "CH4": "CH4" - } - - 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 dictionary of MUSICA variable names, values, and units -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)) - - # 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 waccmKey not 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("{} = object {}".format(waccmKey, chemSinglePoint)) - logger.info("{} = value {} {}".format(waccmKey, chemSinglePoint.values, chemSinglePoint.units)) - musicaTuple = (waccmKey, float(chemSinglePoint.values.mean()), chemSinglePoint.units) # from 0-dim array - musicaDict[musicaName] = musicaTuple - - # close the NetCDF file - waccmDataSet.close() - - return (musicaDict) - - -# 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): - - 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) - - -# 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") - - fp.close() - return - - -# Write JSON fragment suitable for my_config.json in MusicBox. -# initValues = dictionary of Musica varnames and (WACCM name, value, units) -def writeInitJSON(initValues, filename): - - # set up dictionary of vars and initial values - dictName = "chemical species" - initConfig = {dictName: {}} - - 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 - - -# 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 - 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 - if os.path.exists(destPath): - shutil.rmtree(destPath) - - # 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("Replace 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 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 - shutil.move(destPath + ".zip", destPath) - - return - - -# 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 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): - lat = safeFloat(myArgs.get("latitude")) - - lon = None - if ("longitude" in myArgs): - lon = safeFloat(myArgs.get("longitude")) - - 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)) - - # Read named variables from WACCM model output. - varValues = readWACCM(getMusicaDictionary(), - lat, lon, retrieveWhen, waccmDir) - logger.info("Original WACCM varValues = {}".format(varValues)) - - # Perform any conversions needed, or derive variables. - varValues = convertWaccm(varValues) - logger.info("Converted WACCM varValues = {}".format(varValues)) - - if (True): - # Write CSV file for MusicBox initial conditions. - csvName = "{}/{}".format(musicaDir, "initial_conditions.csv") - writeInitCSV(varValues, csvName) - - 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 - - -if (__name__ == "__main__"): - main() - - logger.info("End time: {}".format(datetime.datetime.now())) - sys.exit(0) # no error - - -if (__name__ == "__main__"): - main()