-
Notifications
You must be signed in to change notification settings - Fork 11
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
220 config file csv - initial chem species are moved to CSV, and also specified with JSON data #299
base: main
Are you sure you want to change the base?
Changes from all commits
8f9ffb2
945b04c
98a6c8d
98d83b2
5decc90
798482f
c40a670
f0164da
9486ca8
77eaaf5
8596984
47a143e
6bc3449
35da4bf
3175b5b
4dc4b84
05302da
9133563
fe0a335
ec2fc5a
dbff66e
81f75aa
c4c3dd4
a38a3b9
00270fe
5a668a6
897fa90
06a7955
99ecd9a
c176728
7cad82f
3d6a202
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -105,11 +105,94 @@ def from_UI_JSON(self, UI_JSON, species_list, reaction_list): | |||||||||||||||||||
species_concentrations, | ||||||||||||||||||||
reaction_rates) | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
@classmethod | ||||||||||||||||||||
def retrieve_initial_conditions_from_JSON( | ||||||||||||||||||||
self, | ||||||||||||||||||||
path_to_json, | ||||||||||||||||||||
json_object, | ||||||||||||||||||||
reaction_types): | ||||||||||||||||||||
""" | ||||||||||||||||||||
Retrieves initial conditions from CSV file and JSON structures. | ||||||||||||||||||||
If both are present, JSON values will override the CSV values. | ||||||||||||||||||||
|
||||||||||||||||||||
This class method takes a path to a JSON file, a configuration JSON object, | ||||||||||||||||||||
and a list of desired reaction types. | ||||||||||||||||||||
|
||||||||||||||||||||
Args: | ||||||||||||||||||||
path_to_json (str): The path to the JSON file containing the initial conditions and settings. | ||||||||||||||||||||
json_object (dict): The configuration JSON object containing the initial conditions and settings. | ||||||||||||||||||||
reaction_types: Use set like {"ENV", "CONC"} for species concentrations, {"EMIS", "PHOTO"} for reaction rates. | ||||||||||||||||||||
|
||||||||||||||||||||
Returns: | ||||||||||||||||||||
object: A dictionary of name:value pairs. | ||||||||||||||||||||
""" | ||||||||||||||||||||
|
||||||||||||||||||||
# look for that JSON section | ||||||||||||||||||||
if (not 'initial conditions' in json_object): | ||||||||||||||||||||
return({}) | ||||||||||||||||||||
if (len(list(json_object['initial conditions'].keys())) == 0): | ||||||||||||||||||||
return({}) | ||||||||||||||||||||
|
||||||||||||||||||||
# retrieve initial conditions from CSV and JSON | ||||||||||||||||||||
initial_csv = {} | ||||||||||||||||||||
initial_data = {} | ||||||||||||||||||||
|
||||||||||||||||||||
initCond = json_object['initial conditions'] | ||||||||||||||||||||
logger.debug(f"initCond: {initCond}") | ||||||||||||||||||||
if 'filepaths' in initCond: | ||||||||||||||||||||
file_paths = initCond['filepaths'] | ||||||||||||||||||||
|
||||||||||||||||||||
# loop through the CSV files | ||||||||||||||||||||
for file_path in file_paths: | ||||||||||||||||||||
# read initial conditions from CSV file | ||||||||||||||||||||
initial_conditions_path = os.path.join( | ||||||||||||||||||||
os.path.dirname(path_to_json), file_path) | ||||||||||||||||||||
|
||||||||||||||||||||
file_initial_csv = Conditions.read_initial_conditions_from_file( | ||||||||||||||||||||
initial_conditions_path, reaction_types) | ||||||||||||||||||||
logger.debug(f"file_initial_csv = {file_initial_csv}") | ||||||||||||||||||||
|
||||||||||||||||||||
# tranfer conditions from this file to the aggregated dictionary | ||||||||||||||||||||
for one_csv in file_initial_csv: | ||||||||||||||||||||
# give warning if one file CSV overrides a prior CSV | ||||||||||||||||||||
if one_csv in initial_csv: | ||||||||||||||||||||
logger.warning( | ||||||||||||||||||||
"Value {}:{} in file {} will override prior value {}" | ||||||||||||||||||||
.format(one_csv, file_initial_csv[one_csv], | ||||||||||||||||||||
initial_conditions_path, initial_csv[one_csv])) | ||||||||||||||||||||
|
||||||||||||||||||||
initial_csv[one_csv] = file_initial_csv[one_csv] | ||||||||||||||||||||
|
||||||||||||||||||||
logger.debug(f"initial_csv = {initial_csv}") | ||||||||||||||||||||
|
||||||||||||||||||||
if 'data' in initCond: | ||||||||||||||||||||
# read initial conditions from in-place CSV (list of headers and list of values) | ||||||||||||||||||||
dataConditions = initCond['data'] | ||||||||||||||||||||
initial_data = Conditions.read_data_values_from_table(dataConditions, | ||||||||||||||||||||
reaction_types) | ||||||||||||||||||||
logger.debug(f"initial_data = {initial_data}") | ||||||||||||||||||||
|
||||||||||||||||||||
# override the CSV species initial values with JSON data | ||||||||||||||||||||
numCSV = len(initial_csv) | ||||||||||||||||||||
numData = len(initial_data) | ||||||||||||||||||||
if (numCSV > 0 and numData > 0): | ||||||||||||||||||||
logger.warning(f"Initial data values ({numData}) from JSON will override initial values ({numCSV}) from CSV.") | ||||||||||||||||||||
for one_data in initial_data: | ||||||||||||||||||||
chem_name_alone = one_data.split(".")[1] # remove reaction type | ||||||||||||||||||||
chem_name_alone = chem_name_alone.split(" ")[0] # remove units | ||||||||||||||||||||
initial_csv[chem_name_alone] = initial_data[one_data] | ||||||||||||||||||||
|
||||||||||||||||||||
logger.debug(f"Overridden initial_csv = {initial_csv}") | ||||||||||||||||||||
|
||||||||||||||||||||
return(initial_csv) | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
@classmethod | ||||||||||||||||||||
def from_config_JSON( | ||||||||||||||||||||
self, | ||||||||||||||||||||
path_to_json, | ||||||||||||||||||||
object): | ||||||||||||||||||||
json_object): | ||||||||||||||||||||
""" | ||||||||||||||||||||
Creates an instance of the class from a configuration JSON object. | ||||||||||||||||||||
|
||||||||||||||||||||
|
@@ -118,51 +201,43 @@ def from_config_JSON( | |||||||||||||||||||
|
||||||||||||||||||||
Args: | ||||||||||||||||||||
path_to_json (str): The path to the JSON file containing the initial conditions and settings. | ||||||||||||||||||||
object (dict): The configuration JSON object containing the initial conditions and settings. | ||||||||||||||||||||
json_object (dict): The configuration JSON object containing the initial conditions and settings. | ||||||||||||||||||||
|
||||||||||||||||||||
Returns: | ||||||||||||||||||||
object: An instance of the Conditions class with the settings from the configuration JSON object. | ||||||||||||||||||||
""" | ||||||||||||||||||||
pressure = convert_pressure( | ||||||||||||||||||||
object['environmental conditions']['pressure'], | ||||||||||||||||||||
json_object['environmental conditions']['pressure'], | ||||||||||||||||||||
'initial value') | ||||||||||||||||||||
|
||||||||||||||||||||
temperature = convert_temperature( | ||||||||||||||||||||
object['environmental conditions']['temperature'], | ||||||||||||||||||||
json_object['environmental conditions']['temperature'], | ||||||||||||||||||||
'initial value') | ||||||||||||||||||||
|
||||||||||||||||||||
# Set initial species concentrations | ||||||||||||||||||||
initial_concentrations = {} | ||||||||||||||||||||
reaction_rates = {} | ||||||||||||||||||||
|
||||||||||||||||||||
# reads initial conditions from csv if it is given | ||||||||||||||||||||
if 'initial conditions' in object and len( | ||||||||||||||||||||
list(object['initial conditions'].keys())) > 0: | ||||||||||||||||||||
|
||||||||||||||||||||
initial_conditions_path = os.path.join( | ||||||||||||||||||||
os.path.dirname(path_to_json), | ||||||||||||||||||||
list(object['initial conditions'].keys())[0]) | ||||||||||||||||||||
# we will read species concentrations and reaction rates on two passes | ||||||||||||||||||||
species_concentrations = Conditions.retrieve_initial_conditions_from_JSON( | ||||||||||||||||||||
path_to_json, json_object, {"ENV", "CONC"}) | ||||||||||||||||||||
reaction_rates = Conditions.retrieve_initial_conditions_from_JSON( | ||||||||||||||||||||
path_to_json, json_object, {"EMIS", "PHOTO", "LOSS"}) | ||||||||||||||||||||
Comment on lines
+218
to
+221
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would separate environmental conditions from species concentrations here, just for clarity |
||||||||||||||||||||
|
||||||||||||||||||||
reaction_rates = Conditions.read_initial_rates_from_file( | ||||||||||||||||||||
initial_conditions_path) | ||||||||||||||||||||
# override presure and temperature | ||||||||||||||||||||
if ("pressure" in species_concentrations): | ||||||||||||||||||||
pressure = species_concentrations["pressure"] | ||||||||||||||||||||
if ("temperature" in species_concentrations): | ||||||||||||||||||||
temperature = species_concentrations["temperature"] | ||||||||||||||||||||
|
||||||||||||||||||||
# reads from config file directly if present | ||||||||||||||||||||
if 'chemical species' in object: | ||||||||||||||||||||
initial_concentrations = { | ||||||||||||||||||||
species: convert_concentration( | ||||||||||||||||||||
object['chemical species'][species], 'initial value', temperature, pressure | ||||||||||||||||||||
) | ||||||||||||||||||||
for species in object['chemical species'] | ||||||||||||||||||||
} | ||||||||||||||||||||
logger.debug(f"Returning species_concentrations = {species_concentrations}") | ||||||||||||||||||||
logger.debug(f"Returning reaction_rates = {reaction_rates}") | ||||||||||||||||||||
|
||||||||||||||||||||
return self( | ||||||||||||||||||||
pressure, | ||||||||||||||||||||
temperature, | ||||||||||||||||||||
initial_concentrations, | ||||||||||||||||||||
species_concentrations, | ||||||||||||||||||||
reaction_rates) | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
@classmethod | ||||||||||||||||||||
def read_initial_rates_from_file(cls, file_path): | ||||||||||||||||||||
def read_initial_conditions_from_file(cls, file_path, react_types=None): | ||||||||||||||||||||
""" | ||||||||||||||||||||
Reads initial reaction rates from a file. | ||||||||||||||||||||
|
||||||||||||||||||||
|
@@ -171,14 +246,15 @@ def read_initial_rates_from_file(cls, file_path): | |||||||||||||||||||
|
||||||||||||||||||||
Args: | ||||||||||||||||||||
file_path (str): The path to the file containing the initial reaction rates. | ||||||||||||||||||||
react_types = set of reaction types only to include, or None to include all. | ||||||||||||||||||||
|
||||||||||||||||||||
Returns: | ||||||||||||||||||||
dict: A dictionary of initial reaction rates. | ||||||||||||||||||||
""" | ||||||||||||||||||||
|
||||||||||||||||||||
reaction_rates = {} | ||||||||||||||||||||
|
||||||||||||||||||||
df = pd.read_csv(file_path) | ||||||||||||||||||||
df = pd.read_csv(file_path, skipinitialspace=True) | ||||||||||||||||||||
rows, _ = df.shape | ||||||||||||||||||||
if rows > 1: | ||||||||||||||||||||
raise ValueError(f'Initial conditions file ({file_path}) may only have one row of data. There are {rows} rows present.') | ||||||||||||||||||||
|
@@ -196,10 +272,63 @@ def read_initial_rates_from_file(cls, file_path): | |||||||||||||||||||
rate_name = f'{reaction_type}.{label}' | ||||||||||||||||||||
if rate_name in reaction_rates: | ||||||||||||||||||||
raise ValueError(f"Duplicate reaction rate found: {rate_name}") | ||||||||||||||||||||
reaction_rates[rate_name] = df.iloc[0][key] | ||||||||||||||||||||
|
||||||||||||||||||||
# are we looking for this type? | ||||||||||||||||||||
if (react_types): | ||||||||||||||||||||
if (reaction_type not in react_types): | ||||||||||||||||||||
continue | ||||||||||||||||||||
|
||||||||||||||||||||
# create key-value pair of chemical-concentration | ||||||||||||||||||||
# initial concentration looks like this: CONC.a-pinene [mol m-3] | ||||||||||||||||||||
# reaction rate looks like this: LOSS.SOA2 wall loss.s-1 | ||||||||||||||||||||
chem_name_alone = f"{reaction_type}.{label}" # reaction | ||||||||||||||||||||
if len(parts) == 2: | ||||||||||||||||||||
chem_name_alone = label.split(' ')[0] # strip off [units] to get chemical | ||||||||||||||||||||
reaction_rates[chem_name_alone] = df.at[0, key] # retrieve (row, column) | ||||||||||||||||||||
|
||||||||||||||||||||
return reaction_rates | ||||||||||||||||||||
|
||||||||||||||||||||
@classmethod | ||||||||||||||||||||
def read_data_values_from_table(cls, data_json, react_types=None): | ||||||||||||||||||||
""" | ||||||||||||||||||||
Reads data values from a CSV-type table expressed in JSON. | ||||||||||||||||||||
|
||||||||||||||||||||
This class method takes a JSON element, reads two rows, and | ||||||||||||||||||||
sets variable names and values to the header and value rows. | ||||||||||||||||||||
Example of the data: | ||||||||||||||||||||
"data": [ | ||||||||||||||||||||
["ENV.temperature [K]", "ENV.pressure [Pa]", "CONC.A [mol m-3]", "CONC.B [mol m-3]"], | ||||||||||||||||||||
[200, 70000, 0.67, 2.3e-9] | ||||||||||||||||||||
] | ||||||||||||||||||||
|
||||||||||||||||||||
Args: | ||||||||||||||||||||
data_json (object): JSON list of two lists. | ||||||||||||||||||||
react_types = set of reaction types only to include, or None to include all. | ||||||||||||||||||||
|
||||||||||||||||||||
Returns: | ||||||||||||||||||||
dict: A dictionary of initial data values. | ||||||||||||||||||||
""" | ||||||||||||||||||||
|
||||||||||||||||||||
data_values = {} | ||||||||||||||||||||
|
||||||||||||||||||||
rows = len(data_json) | ||||||||||||||||||||
if rows != 2: | ||||||||||||||||||||
raise ValueError(f'Initial conditions data in JSON ({data_json}) should have only header and value rows. There are {rows} rows present.') | ||||||||||||||||||||
|
||||||||||||||||||||
# build the dictionary from the columns | ||||||||||||||||||||
header_row = data_json[0] | ||||||||||||||||||||
value_row = data_json[1] | ||||||||||||||||||||
for header, value in zip(header_row, value_row): | ||||||||||||||||||||
# are we looking for this type? | ||||||||||||||||||||
if (react_types): | ||||||||||||||||||||
header_type = header.split('.')[0] | ||||||||||||||||||||
if (header_type not in react_types): | ||||||||||||||||||||
continue | ||||||||||||||||||||
|
||||||||||||||||||||
data_values[header] = float(value) | ||||||||||||||||||||
Comment on lines
+321
to
+328
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think pandas may be able to handle this. In general I think we should prefer to push parsing into pandas as much as possible |
||||||||||||||||||||
|
||||||||||||||||||||
return data_values | ||||||||||||||||||||
|
||||||||||||||||||||
def add_species_concentration(self, species_concentration): | ||||||||||||||||||||
""" | ||||||||||||||||||||
Add a SpeciesConcentration instance to the list of species concentrations. | ||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
ENV.temperature [K],ENV.pressure [Pa],CONC.A [mol m-3],CONC.B [mol m-3],CONC.C [mol m-3] | ||
283.6,102364.4,0.9,0.1,0.3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only recently learned about
classmethod
andstaticmethod
. But, apparently it's common in python to usecls
instead ofself
forclassmethod
s.