-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added functions to read and write a nuopc.runconfig file.
- Loading branch information
1 parent
5c62a3f
commit 105fd69
Showing
2 changed files
with
186 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
"""NUOPC configuration""" | ||
|
||
from pathlib import Path | ||
import re | ||
|
||
|
||
def _convert_from_string(value: str): | ||
"""Tries to convert a string to the most appropriate type. Leaves the string unchanged if not conversion succeeds. | ||
:param value: value to convert. | ||
""" | ||
# Start by trying to convert from a Fortran logical to a Python bool | ||
if value.lower() == ".true.": | ||
return True | ||
elif value.lower() == ".false.": | ||
return False | ||
# Next try to convert to integer or float | ||
for conversion in [ | ||
lambda: int(value), | ||
lambda: float(value), | ||
lambda: float(value.replace("D", "e")), | ||
]: | ||
try: | ||
out = conversion() | ||
except ValueError: | ||
continue | ||
return out | ||
# None of the above succeeded, so just return the string | ||
return value | ||
|
||
|
||
def _convert_to_string(value) -> str: | ||
"""Converts values to a string. | ||
:param value: value to convert. | ||
""" | ||
if isinstance(value, bool): | ||
return ".true." if value else ".false." | ||
elif isinstance(value, float): | ||
return "{:e}".format(value).replace("e", "D") | ||
else: | ||
return str(value) | ||
|
||
|
||
def read_nuopc_config(file_name: str) -> dict: | ||
"""Read a NUOPC config file. | ||
:param file_name: File name. | ||
""" | ||
fname = Path(file_name) | ||
if not fname.is_file(): | ||
raise FileNotFoundError(f"File not found: {fname.as_posix()}") | ||
|
||
label_value_pattern = re.compile(r"\s*(\w+)\s*:\s*(.+)\s*") | ||
table_start_pattern = re.compile(r"\s*(\w+)\s*::\s*") | ||
table_end_pattern = re.compile(r"\s*::\s*") | ||
assignment_pattern = re.compile(r"\s*(\w+)\s*=\s*(\S+)\s*") | ||
|
||
config = {} | ||
with open(fname, "r") as stream: | ||
reading_table = False | ||
label = None | ||
table = None | ||
for line in stream: | ||
line = re.sub(r"(#).*", "", line) | ||
if line.strip(): | ||
if reading_table: | ||
if re.match(table_end_pattern, line): | ||
config[label] = table | ||
reading_table = False | ||
else: | ||
match = re.match(assignment_pattern, line) | ||
if match: | ||
table[match.group(1)] = _convert_from_string(match.group(2)) | ||
else: | ||
raise ValueError( | ||
f"Line: {line} in file {file_name} is not a valid NUOPC configuration specification" | ||
) | ||
|
||
elif re.match(table_start_pattern, line): | ||
reading_table = True | ||
match = re.match(label_value_pattern, line) | ||
label = match.group(1) | ||
table = {} | ||
|
||
elif re.match(label_value_pattern, line): | ||
match = re.match(label_value_pattern, line) | ||
if len(match.group(2).split()) > 1: | ||
config[match.group(1)] = [ | ||
_convert_from_string(string) | ||
for string in match.group(2).split() | ||
] | ||
else: | ||
config[match.group(1)] = _convert_from_string(match.group(2)) | ||
|
||
return config | ||
|
||
|
||
def write_nuopc_config(config: dict, file: Path): | ||
"""Write a NUOPC config dictionary as a Resource File. | ||
:param config: Dictionary holding the NUOPC configuration to write | ||
:param file: File to write to. | ||
""" | ||
with open(file, "w") as stream: | ||
for key, item in config.items(): | ||
if isinstance(item, dict): | ||
stream.write(key + "::\n") | ||
for label, value in item.items(): | ||
stream.write( | ||
" " + label + " = " + _convert_to_string(value) + "\n" | ||
) | ||
stream.write("::\n\n") | ||
else: | ||
stream.write( | ||
key + ": " + " ".join(map(_convert_to_string, item)) + "\n" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import pytest | ||
import filecmp | ||
|
||
from utils import MockFile | ||
from om3utils.nuopc_config import read_nuopc_config, write_nuopc_config | ||
|
||
|
||
@pytest.fixture() | ||
def simple_nuopc_config(): | ||
return dict( | ||
DRIVER_attributes={ | ||
"Verbosity": "off", | ||
"cime_model": "cesm", | ||
"logFilePostFix": ".log", | ||
"pio_blocksize": -1, | ||
"pio_rearr_comm_enable_hs_comp2io": True, | ||
"pio_rearr_comm_enable_hs_io2comp": False, | ||
"reprosum_diffmax": -1.0e-8, | ||
"wv_sat_table_spacing": 1.0, | ||
"wv_sat_transition_start": 20.0, | ||
}, | ||
COMPONENTS=["atm", "ocn"], | ||
ALLCOMP_attributes={ | ||
"ATM_model": "datm", | ||
"GLC_model": "sglc", | ||
"OCN_model": "mom", | ||
"ocn2glc_levels": "1:10:19:26:30:33:35", | ||
}, | ||
) | ||
|
||
|
||
@pytest.fixture() | ||
def simple_nuopc_config_file(tmp_path): | ||
file = tmp_path / "simple_config_file" | ||
resource_file_str = """DRIVER_attributes:: | ||
Verbosity = off | ||
cime_model = cesm | ||
logFilePostFix = .log | ||
pio_blocksize = -1 | ||
pio_rearr_comm_enable_hs_comp2io = .true. | ||
pio_rearr_comm_enable_hs_io2comp = .false. | ||
reprosum_diffmax = -1.000000D-08 | ||
wv_sat_table_spacing = 1.000000D+00 | ||
wv_sat_transition_start = 2.000000D+01 | ||
:: | ||
COMPONENTS: atm ocn | ||
ALLCOMP_attributes:: | ||
ATM_model = datm | ||
GLC_model = sglc | ||
OCN_model = mom | ||
ocn2glc_levels = 1:10:19:26:30:33:35 | ||
:: | ||
""" | ||
return MockFile(file, resource_file_str) | ||
|
||
|
||
def test_read_nuopc_config(tmp_path, simple_nuopc_config, simple_nuopc_config_file): | ||
config_from_file = read_nuopc_config(file_name=simple_nuopc_config_file.file) | ||
|
||
assert config_from_file == simple_nuopc_config | ||
|
||
|
||
def test_write_nuopc_config(tmp_path, simple_nuopc_config, simple_nuopc_config_file): | ||
file = tmp_path / "config_file" | ||
write_nuopc_config(simple_nuopc_config, file) | ||
|
||
assert filecmp.cmp(file, simple_nuopc_config_file.file) |