Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command-line option for python MusicBox #172

Merged
merged 23 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
652fe37
Hello World from the new MusicBox main().
carl-drews Jun 6, 2024
5e5b6be
Python main() is producing valid floating-point output.
carl-drews Jun 11, 2024
321d2b9
Added command-line arguments.
carl-drews Jun 24, 2024
509037b
Write out the solution.
carl-drews Jun 25, 2024
5851f6e
Import music_box_logger the right way.
carl-drews Jun 26, 2024
be55094
Move log statements into main().
carl-drews Jun 28, 2024
a388a2b
Removed realistic physical defaults, replaced with None.
carl-drews Jun 28, 2024
b1cd963
Throw exception for None solver instead of Warning.
carl-drews Jul 1, 2024
bf52a88
Removed ACOM Logger. Set up music_box operation as executable script …
carl-drews Jul 2, 2024
1893020
Use argparse and configure it for key=value pairs.
carl-drews Jul 2, 2024
37aeb2f
Revise command-line arguments to configFile and outputDir.
carl-drews Jul 2, 2024
287f23e
pull smoke test action from other branch
mattldawson Jul 3, 2024
bb13ecd
Modified to look like the MusicBox template.
carl-drews Jul 3, 2024
2597677
Display the working directory.
carl-drews Jul 3, 2024
71f0b83
Removed redundant pip install.
carl-drews Jul 3, 2024
3e0072a
Action test (#174)
K20shores Jul 9, 2024
b739bd7
Re-added file that was inadvertently deleted, and updated musica vers…
carl-drews Jul 12, 2024
1ca853f
Removed helper scripts from repository.
carl-drews Jul 12, 2024
3c4a16d
Removed another batch helper script.
carl-drews Jul 12, 2024
ff332ca
Check for configFile parameter. Give error and example if not found.
carl-drews Jul 13, 2024
0b3989e
Add key=value required parameter to argparse help.
carl-drews Jul 13, 2024
23740dd
Rename smoke test to CI Tests. Deleted test.yml.
carl-drews Jul 15, 2024
23a87e6
Merge branch 'main' into carls-main01
carl-drews Jul 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions .github/workflows/test.yml → .github/workflows/CI_Tests.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name: build
name: CI Tests

on: [push, workflow_dispatch]

jobs:
build:
continue-on-error: true
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
Expand All @@ -20,17 +21,13 @@ jobs:
python-version: '3.9'
cache: 'pip'

- run: pip install -r requirements.txt
- name: Install dependencies
run: python -m pip install --upgrade pip

- name: Install this package
run: pip install -e .

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Run tests
- name: Run the smoke tests
run: |
cd tests
pytest
music_box configFile=tests/configs/analytical_config/my_config.json outputDir=tests/configs/analytical_config

7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ classifiers = ["License :: OSI Approved :: Apache Software License"]
dynamic = ["version", "description"]

dependencies = [
"musica==0.6.1.dev0"
"musica==0.7.0"
]

[project.urls]
Home = "https://github.com/NCAR/music-box"
Home = "https://github.com/NCAR/music-box"

[project.scripts]
music_box = "acom_music_box.music_box_main:main"
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
musica==0.6.1.dev0
musica==0.7.0
pandas
pipx
pytest
Expand Down
57 changes: 38 additions & 19 deletions src/acom_music_box/music_box.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import json
import os

import logging
logger = logging.getLogger(__name__)

from .music_box_evolving_conditions import EvolvingConditions
from .music_box_reaction_list import ReactionList
from .music_box_reaction import Reaction, Branched, Arrhenius, Tunneling, Troe_Ternary
from .music_box_species_list import SpeciesList
from .music_box_model_options import BoxModelOptions
from .music_box_conditions import Conditions

import csv
import musica



class MusicBox:
"""
Represents a box model with attributes such as box model options, species list, reaction list,
Expand All @@ -36,13 +42,15 @@ def __init__(self, box_model_options=None, species_list=None, reaction_list=None
evolving_conditions (List[EvolvingConditions]): List of evolving conditions over time.
config_file (String): File path for the configuration file to be located. Default is "camp_data/config.json".
"""
self.box_model_options = box_model_options
self.box_model_options = box_model_options if box_model_options is not None else BoxModelOptions()
self.species_list = species_list if species_list is not None else SpeciesList()
self.reaction_list = reaction_list if reaction_list is not None else ReactionList()
self.initial_conditions = initial_conditions
self.initial_conditions = initial_conditions if initial_conditions is not None else Conditions()
self.evolving_conditions = evolving_conditions if evolving_conditions is not None else EvolvingConditions([], [])
self.config_file = config_file if config_file is not None else "camp_data/config.json"

self.solver = None



def add_evolving_condition(self, time_point, conditions):
Expand Down Expand Up @@ -409,7 +417,7 @@ def create_solver(self, path_to_config):
None
"""
# Create a solver object using the configuration file
self.solver = musica.create_micm(path_to_config)
self.solver = musica.create_solver(path_to_config)


def solve(self, path_to_output = None):
Expand All @@ -428,7 +436,7 @@ def solve(self, path_to_output = None):
list: A 2D list where each inner list represents the results of the simulation
at a specific time step.
"""

#sets up initial conditions to be current conditions
curr_conditions = self.initial_conditions

Expand All @@ -448,7 +456,7 @@ def solve(self, path_to_output = None):
next_conditions_index = 1
next_conditions = self.evolving_conditions.conditions[1]
next_conditions_time = self.evolving_conditions.times[1]


#initalizes output headers
output_array = []
Expand All @@ -458,10 +466,15 @@ def solve(self, path_to_output = None):
headers.append("ENV.temperature")
headers.append("ENV.pressure")


if (self.solver is None):
raise Exception("Error: MusicBox object {} has no solver."
.format(self))
rate_constant_ordering = musica.user_defined_reaction_rates(self.solver)

species_constant_ordering = musica.species_ordering(self.solver)


#adds species headers to output
ordered_species_headers = [k for k, v in sorted(species_constant_ordering.items(), key=lambda item: item[1])]
for spec in ordered_species_headers:
Expand All @@ -478,9 +491,9 @@ def solve(self, path_to_output = None):
next_output_time = curr_time
#runs the simulation at each timestep




while(curr_time <= self.box_model_options.simulation_length):

#outputs to output_array if enough time has elapsed
if(next_output_time <= curr_time):
row = []
Expand All @@ -507,30 +520,36 @@ def solve(self, path_to_output = None):

else:
next_conditions = None




# calculate air density from the ideal gas law
BOLTZMANN_CONSTANT = 1.380649e-23
AVOGADRO_CONSTANT = 6.02214076e23;
GAS_CONSTANT = BOLTZMANN_CONSTANT * AVOGADRO_CONSTANT
air_density = curr_conditions.pressure / (GAS_CONSTANT * curr_conditions.temperature)

#updates M accordingly
if 'M' in species_constant_ordering:
BOLTZMANN_CONSTANT = 1.380649e-23
AVOGADRO_CONSTANT = 6.02214076e23;
GAS_CONSTANT = BOLTZMANN_CONSTANT * AVOGADRO_CONSTANT
ordered_concentrations[species_constant_ordering['M']] = curr_conditions.pressure / (GAS_CONSTANT * curr_conditions.temperature)
ordered_concentrations[species_constant_ordering['M']] = air_density

#solves and updates concentration values in concentration array
musica.micm_solve(self.solver, self.box_model_options.chem_step_time, curr_conditions.temperature, curr_conditions.pressure, ordered_concentrations, ordered_rate_constants)


if (not ordered_concentrations):
logger.info("Warning: ordered_concentrations list is empty.")
musica.micm_solve(self.solver, self.box_model_options.chem_step_time,
curr_conditions.temperature, curr_conditions.pressure, air_density,
ordered_concentrations, ordered_rate_constants)


#increments time
curr_time += self.box_model_options.chem_step_time

#outputs to file if output is present
if(path_to_output != None):
logger.info("path_to_output = {}".format(path_to_output))
with open(path_to_output, 'w', newline='') as output:
writer = csv.writer(output)
writer.writerows(output_array)

#returns output_array
return output_array

Expand Down
3 changes: 2 additions & 1 deletion src/acom_music_box/music_box_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class Conditions:
reactionRates (List[ReactionRate]): A list of reaction rates.
"""

def __init__(self, pressure, temperature, species_concentrations=None, reaction_rates=None):
def __init__(self, pressure=None, temperature=None, species_concentrations=None, reaction_rates=None):

"""
Initializes a new instance of the Conditions class.

Expand Down
90 changes: 90 additions & 0 deletions src/acom_music_box/music_box_main.py
carl-drews marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from acom_music_box import MusicBox


import math
import datetime
import sys

import logging
logger = logging.getLogger(__name__)

import argparse
import os



# 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)



def main():
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logger.info("{}".format(__file__))
logger.info("Start time: {}".format(datetime.datetime.now()))

logger.info("Hello, MusicBox World!")
logger.info("Working directory = {}".format(os.getcwd()))

# retrieve and parse the command-line arguments
myArgs = getArgsDictionary(sys.argv[1:])
logger.info("Command line = {}".format(myArgs))

# set up the home configuration file
musicBoxConfigFile = None
if ("configFile" in myArgs):
musicBoxConfigFile = myArgs.get("configFile")

# set up the output directory
musicBoxOutputDir = ".\\" # default
if ("outputDir" in myArgs):
musicBoxOutputDir = myArgs.get("outputDir")

# check for required arguments and provide examples
if (musicBoxConfigFile is None):
errorString = "Error: The configFile parameter is required."
errorString += (" Example: configFile={}"
.format(os.path.join("tests", "configs", "analytical_config", "my_config.json")))
raise Exception(errorString)

# create and load a MusicBox object
myBox = MusicBox()
myBox.readConditionsFromJson(musicBoxConfigFile)
logger.info("myBox = {}".format(myBox))

# create solver and solve, writing output to requested directory
campConfig = os.path.join(os.path.dirname(musicBoxConfigFile), myBox.config_file)
logger.info("CAMP config = {}".format(campConfig))
myBox.create_solver(campConfig)
logger.info("myBox.solver = {}".format(myBox.solver))
mySolution = myBox.solve(os.path.join(musicBoxOutputDir, "mySolution.csv"))
logger.info("mySolution = {}".format(mySolution))

logger.info("End time: {}".format(datetime.datetime.now()))
sys.exit(0)


if __name__ == "__main__":
main()
3 changes: 2 additions & 1 deletion src/acom_music_box/music_box_model_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class BoxModelOptions:
simulationLength (float): Length of the simulation in hours.
"""

def __init__(self, chem_step_time, output_step_time, simulation_length, grid="box"):
def __init__(self, chem_step_time=None, output_step_time=None, simulation_length=None, grid="box"):

"""
Initializes a new instance of the BoxModelOptions class.

Expand Down