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 utility to compare Fortran namelists #1234

Merged
Changes from all commits
Commits
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
107 changes: 107 additions & 0 deletions ush/compare_f90nml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env python3

import json
import f90nml
from typing import Dict
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter


def get_dict_from_nml(filename: str) -> Dict:
"""
Read a F90 namelist and convert to a dictionary.
This method uses json to convert OrderedDictionary into regular dictionary
Parameters
----------
filename: str
Name of the F90 namelist
Returns
-------
dictionary: Dict
F90 namelist returned as a dictionary
"""
return json.loads(json.dumps(f90nml.read(filename).todict()))


def compare_dicts(dict1: Dict, dict2: Dict, path: str = "") -> None:
"""
Compare 2 dictionaries.
This is done by looping over keys in dictionary 1 and searching for them
in dictionary 2.
If a matching key is found, the values are compared.
If a matching key is not found, it is set to as UNDEFINED.
Note: A reverse match is not performed in this method. For reverse matching, use the -r option in the main driver.
WalterKolczynski-NOAA marked this conversation as resolved.
Show resolved Hide resolved
Note: This is a recursive method to handle nested dictionaries.
Parameters
----------
dict1: Dict
First dictionary
dict2: Dict
Second dictionary
path: str (optional)
default: ""
key (if nested dictionary)
Returns
-------
None
"""

result = dict()
for kk in dict1.keys(): # Loop over all keys of first dictionary
if kk in dict2.keys(): # kk is present in dict2
if isinstance(dict1[kk], dict): # nested dictionary, go deeper
compare_dicts(dict1[kk], dict2[kk], path=kk)
else:
if dict1[kk] != dict2[kk]:
if path not in result:
result[path] = dict()
result[path][kk] = [dict1[kk], dict2[kk]]
else: # kk is *not* present in dict2
tt = path if path else kk
if tt not in result:
result[tt] = dict()
result[tt][kk] = [dict1[kk], 'UNDEFINED']

def _print_diffs(diff_dict: Dict) -> None:
"""
Print the differences between the two dictionaries to stdout
Parameters
----------
diff_dict: Dict
Dictionary containing differences
Returns
-------
None
"""
for path in diff_dict.keys():
print(f"{path}:")
max_len = len(max(diff_dict[path], key=len))
for kk in diff_dict[path].keys():
items = diff_dict[path][kk]
print(
f"{kk:>{max_len+2}} : {' | '.join(map(str, diff_dict[path][kk]))}")

_print_diffs(result)


if __name__ == "__main__":

parser = ArgumentParser(
description=("Compare two Fortran namelists and display differences (left_namelist - right_namelist)"),
formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('left_namelist', type=str, help="Left namelist to compare")
parser.add_argument('right_namelist', type=str, help="Right namelist to compare")
parser.add_argument('-r', '--reverse', help='reverse diff (right_namelist - left_namelist)',
action='store_true', required=False)
args = parser.parse_args()

nml1, nml2 = args.left_namelist, args.right_namelist
if args.reverse:
nml2, nml1 = nml1, nml2

dict1 = get_dict_from_nml(nml1)
dict2 = get_dict_from_nml(nml2)

msg = f"comparing: {nml1} | {nml2}"
print(msg)
print("-" * len(msg))
compare_dicts(dict1, dict2)