Skip to content

Commit 93c25fc

Browse files
authored
Merge pull request #780 from rarutter/presim_script
This adds some utility python methods and the start of a pre-simulation report script to help the user check settings, file existence, input value validity, etc.
2 parents b91aee7 + 1fdf926 commit 93c25fc

File tree

4 files changed

+313
-10
lines changed

4 files changed

+313
-10
lines changed

scripts/tests/presim_summary.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python
2+
3+
import os
4+
import util
5+
import util.input
6+
import util.param
7+
import util.runmask
8+
import util.config
9+
import commentjson
10+
11+
12+
verbose = True
13+
14+
#Print expected config file path
15+
#Load expected config file
16+
assumed_config_file = "config/config.js"
17+
print("Loading " + assumed_config_file)
18+
with open(assumed_config_file) as config_file:
19+
config = commentjson.load(config_file)
20+
21+
22+
#Extract and display input file path prefixes (excluding parameter files
23+
# and output_spec)
24+
input_filepaths = util.config.get_input_paths(assumed_config_file)
25+
path_prefixes = []
26+
for filepath in input_filepaths:
27+
prefix = os.path.dirname(filepath)
28+
if prefix not in path_prefixes:
29+
path_prefixes.append(prefix)
30+
print("*********************************************************")
31+
print("|", len(path_prefixes), "input path prefixes specified:")
32+
for prefix in path_prefixes:
33+
print("|", prefix)
34+
print("*********************************************************")
35+
36+
37+
#Check that all input files specified in the config file exist (excluding
38+
# parameter files and output_spec)
39+
#We are not using the following method because that checks a whole
40+
# directory, which would not allow individually different input paths
41+
#util.input.check_input_set_existence(input directory)
42+
missing_files = [path for path in input_filepaths if not os.path.exists(path)]
43+
if len(missing_files) > 0:
44+
star_len = len(max(missing_files, key=len)) + 4
45+
star_str = '*' * star_len
46+
47+
print(star_str)
48+
print("| Missing input files:")
49+
print("|", *missing_files)
50+
print(star_str)
51+
else:
52+
print("***********************************")
53+
print("| All specified input files exist |")
54+
print("***********************************")
55+
56+
57+
#Find total count of active cells
58+
#The call to cell_count will also flag invalid values
59+
mask_info = util.runmask.cell_count(config['IO']['runmask_file'], verbose)
60+
invalid_values = {value:count for value,count in mask_info.items() if value not in [0,1]}
61+
62+
print("*********************************************************")
63+
print("| Active cells in run mask:", mask_info[1])
64+
print("| Inactive cells in run mask:", mask_info[0])
65+
print("| Other values:", invalid_values)
66+
print("*********************************************************")
67+
68+
69+
#Check that all CMT values specified in the input vegetation file
70+
# have a parameterization in the parameter files.
71+
input_CMTs = util.input.get_vegtypes(config['IO']['veg_class_file'], verbose)
72+
print("Input veg types:", *input_CMTs.keys(), sep=', ')
73+
print("input veg counts:", *input_CMTs.values(), sep=', ')
74+
print(input_CMTs)
75+
76+
param_CMTs = util.param.get_available_CMTs(config['IO']['parameter_dir'])
77+
print("Param CMTs:", param_CMTs)
78+
79+
unparameterized_CMTs = [x for x in input_CMTs.keys() if x not in param_CMTs]
80+
81+
if len(unparameterized_CMTs) > 0:
82+
print("There are CMTs in the veg file that have no parameterization")
83+
print(unparameterized_CMTs)
84+
print("Check that these are masked out by the run mask or set to\
85+
different values before running.")
86+
else:
87+
print("************************************************")
88+
print("| All input CMTs exist in the parameter files. |")
89+
print("| Please ensure that all are calibrated. |")
90+
print("************************************************")
91+
92+

scripts/util/config.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env python
2+
3+
import sys
4+
import os
5+
import argparse
6+
import textwrap
7+
8+
import commentjson
9+
10+
11+
def get_input_paths(config_file="config/config.js", verbose=True):
12+
'''
13+
'''
14+
with open(config_file) as config_fh:
15+
config = commentjson.load(config_fh)
16+
17+
#Should this also handle parameter_dir and output_spec file location?
18+
#At least for now, no. The matching 'set_input_paths' method should likely
19+
# only apply to this set of files.
20+
input_filenames = ['hist_climate_file', 'proj_climate_file',
21+
'veg_class_file', 'drainage_file', 'soil_texture_file',
22+
'co2_file', 'proj_co2_file', 'runmask_file', 'topo_file',
23+
'fri_fire_file', 'hist_exp_fire_file',
24+
'proj_exp_fire_file']
25+
if verbose:
26+
print("Fetching input paths for filenames:", *input_filenames, sep=', ')
27+
28+
paths = []
29+
for filename in input_filenames:
30+
# path = os.path.dirname(config['IO'][filename])
31+
path = config['IO'][filename]
32+
if path not in paths:
33+
paths.append(path)
34+
35+
return paths
36+
37+
38+
def cmdline_define():
39+
'''Define the command line interface and return the parser object.'''
40+
41+
parser = argparse.ArgumentParser(
42+
formatter_class=argparse.RawDescriptionHelpFormatter,
43+
description=textwrap.dedent('''
44+
Helper script for a dvm-dos-tem config file.
45+
''')
46+
)
47+
parser.add_argument('file', nargs='?', metavar=('FILE'),
48+
help=textwrap.dedent('''The config file to operate on.'''))
49+
50+
parser.add_argument('--verbose', action='store_true',
51+
help=textwrap.dedent('''Print info to stdout when script runs.'''))
52+
53+
parser.add_argument('--get-input-paths', action='store_true',
54+
help=textwrap.dedent('''Returns all input file paths'''))
55+
56+
return parser
57+
58+
def cmdline_parse(argv=None):
59+
'''
60+
The command line interface specification and parser for util config.py.
61+
62+
If parse_args(...) is called with argv=None, then parse_args(...) will
63+
use sys.argv[1:]. Otherwise argv is parsed according to the specification.
64+
65+
Parameters
66+
----------
67+
argv : None or list of strings
68+
arguments that argparse library will parse; if None, then sys.argv[1:] are
69+
parsed.
70+
71+
Returns
72+
-------
73+
args : Namespace generated by argparse
74+
75+
'''
76+
parser = cmdline_define()
77+
78+
args = parser.parse_args(argv)
79+
80+
# print(argv)
81+
# print(args)
82+
83+
# Force vebosity on if user requests showing data
84+
#if args.show:
85+
#args.verbose = True # ?? Not sure about this...
86+
87+
if (args.file is None) or (not os.path.isfile(args.file)):
88+
parser.error("'{}' is an invalid path to a config file!".format(args.file))
89+
90+
return args
91+
92+
93+
def cmdline_run(args):
94+
'''
95+
Executes based on the command line arguments.
96+
97+
Parameters
98+
----------
99+
args : Namespace
100+
Should be a Namespace with all the appropriate arguments.
101+
102+
Returns
103+
-------
104+
exit_code : int
105+
Non-zero if the program cannot complete successfully.
106+
107+
'''
108+
if args.get_input_paths:
109+
get_input_paths(args.file, args.verbose)
110+
111+
return 0
112+
113+
114+
def cmdline_entry(argv=None):
115+
'''
116+
Wrapper allowing for easier testing of the cmdline run and parse functions.
117+
'''
118+
args = cmdline_parse(argv)
119+
return cmdline_run(args)
120+
121+
122+
if __name__ == '__main__':
123+
sys.exit(cmdline_entry()) # this makes sure appropriate exit code is passed on.
124+

scripts/util/input.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import glob
66
import netCDF4 as nc
77
import numpy as np
8+
from collections import Counter
89

910
import argparse
1011
import textwrap
@@ -38,7 +39,7 @@ class MissingInputFilesValueError(ValueError):
3839
class MustSupplyPixelCoordsError(ValueError):
3940
'''Raise when user must supply pixel coordinates(i.e. command line flag --yx)'''
4041

41-
def verify_input_files(in_folder):
42+
def check_input_set_existence(in_folder):
4243
'''
4344
Raises various exceptions if there are problems with the files in the in_folder.
4445
@@ -73,8 +74,36 @@ def verify_input_files(in_folder):
7374
msg = "Missing files: {}".format(required_files.difference(files))
7475
raise MissingInputFilesValueError(msg)
7576

76-
if 'output' not in dirs:
77-
raise MissingInputFilesValueError("'output/' directory not present!")
77+
78+
def get_vegtypes(veg_input, verbose=False):
79+
'''
80+
Returns a Counter containing the CMT values in a specified vegetation
81+
input file, along with a count of the cells for each. Does not check
82+
the validity of CMT values, but does include invalid entries for easy
83+
error checking from the caller.
84+
85+
Parameters
86+
----------
87+
veg_input : str
88+
Filename of the vegetation file to operate on.
89+
'''
90+
91+
with nc.Dataset(veg_input, 'r') as veg_file:
92+
if 'veg_class' not in veg_file.variables.keys():
93+
raise RuntimeError(f'{veg_input} has no veg_class variable')
94+
95+
veg_data = veg_file.variables["veg_class"][:,:]
96+
97+
if verbose:
98+
print(veg_data)
99+
100+
hashable_veg_data = veg_data.flatten()
101+
102+
veg_counts = Counter(hashable_veg_data)
103+
if verbose:
104+
print(veg_counts)
105+
106+
return veg_counts
78107

79108

80109
def crop_attr_string(ys='', xs='', yo='', xo='', msg=''):
@@ -630,9 +659,21 @@ def cmdline_define():
630659
Query one or more dvmdostem inputs for various information.'''))
631660
query_parser.add_argument('--iyix-from-latlon', default=None, nargs=2, type=float,
632661
help="Find closest pixel to provided lat and lon arguments.")
633-
query_parser.add_argument('--latlon-file',
634-
default='../snap-data/temporary-veg-from-LandCover_TEM_2005.tif',
635-
help="The file to read from for getting lat/lon pixel offsets.")
662+
663+
query_parser.add_argument('--check-existence', action='store_true',
664+
help=textwrap.dedent('''Check the files in the specified input\
665+
directory against a hardcoded list of expected files.'''),
666+
default=None)
667+
668+
query_parser.add_argument('--vegtypes', action='store_true',
669+
help=textwrap.dedent('''Returns a Counter with the CMT values and the\
670+
number of times they appear in a vegetation file'''))
671+
672+
query_parser.add_argument('input_folder', help="Path to a folder containing a set of dvmdostem inputs.")
673+
674+
# query_parser.add_argument('--latlon-file',
675+
# default='../snap-data/temporary-veg-from-LandCover_TEM_2005.tif',
676+
# help="The file to read from for getting lat/lon pixel offsets.")
636677

637678
crop_parser = subparsers.add_parser('crop',help=textwrap.dedent('''\
638679
Crop an input dataset, using offset and size. The reason we need this in
@@ -682,17 +723,23 @@ def cmdline_define():
682723
print(args)
683724

684725
if args.command == 'crop':
685-
verify_input_files(args.input_folder)
726+
check_input_set_existence(args.input_folder)
686727
crop_wrapper(args)
687728

688729
if args.command == 'climate-ts-plot':
689-
#verify_input_files(args.input_folder)
730+
#check_input_set_existence(args.input_folder)
690731
climate_ts_plot(args)
691732

692733
if args.command == 'climate-gap-plot':
693734
climate_gap_count_plot(args)
694735

695736
if args.command == 'query':
737+
if args.check_existence:
738+
check_input_set_existence(args.input_folder)
739+
740+
if args.vegtypes:
741+
get_vegtypes(args.veg_file)
742+
696743
if args.iyix_from_latlon:
697744

698745
TMP_NC_FILE = '/tmp/WINNING.nc'

0 commit comments

Comments
 (0)