Skip to content

Commit 8ad34dd

Browse files
authored
Allow for pretty-printing of namelist array variables in log file. (#459)
Tag name (required for release branches): Originator(s): nusbaume Description (include the issue title, and the keyword ['closes', 'fixes', 'resolves'] followed by the issue number): This PR allows for the "pretty-printing" of namelist variables in the SIMA log file that are arrays. Originally the variables were printed in an entirely un-formatted way, which could make it difficult to read. Now array variables are printed to the log like so: ``` aero_phys_props_climate(1) = N:sulf:bulk_sulfate:/glade/campaign/cesm/cesmdata/inputdata/atm/cam/physprops/sulfate_camrt_c080918.nc aero_phys_props_climate(2) = N:dust1:dust_bin1:/glade/campaign/cesm/cesmdata/inputdata/atm/cam/physprops/dust1_camrt_c120518.nc aero_phys_props_climate(3) = N:dust2:dust_bin2:/glade/campaign/cesm/cesmdata/inputdata/atm/cam/physprops/dust2_camrt_c120518.nc aero_phys_props_climate(4) = UNSET aero_phys_props_climate(5) = UNSET ``` The PR also removes a non-varying property in the namelist reader generation code, adds the `iomsg` output to the actual read calls, and now only writes the namelist variable values to the log if the logging level is greater than the minimum. Fixes #450 Describe any changes made to build system: Modifications were made to the namelist reader generation routine. Describe any changes made to the namelist: No changes to the namelist files themselves (just the generated `readnl` routines). List any changes to the defaults for the input datasets (e.g. boundary datasets): N/A List all files eliminated and why: N/A List all files added and what they do: N/A List all existing files that have been modified, and describe the changes: (Helpful git command: `git diff --name-status development...<your_branch_name>`) M cime_config/create_readnl_files.py - Add array pretty-printing, iomsg, and log-level control to generated Fortran. Remove un-needed `log_info` variable. M src/control/runtime_opts.F90 - Add comment indicating the need to read the logging namelist first. M test/unit/python/sample_files/namelist_files/banana_namelist.F90 M test/unit/python/sample_files/namelist_files/kumquat_namelist.F90 - Update testing files to incorporate new Fortran code. M test/unit/python/test_create_readnl_files.py - Add test to make sure array dimension number limit check works correctly. If there are new failures (compared to the `test/existing-test-failures.txt` file), have them OK'd by the gatekeeper, note them here, and add them to the file. If there are baseline differences, include the test and the reason for the diff. What is the nature of the change? Roundoff? derecho/intel/aux_sima: ALL PASS excluding expected `multitape` failure. derecho/gnu/aux_sima: ALL PASS If this changes climate describe any run(s) done to evaluate the new climate in enough detail that it(they) could be reproduced: CAM-SIMA date used for the baseline comparison tests if different than latest:
1 parent c2a0fbc commit 8ad34dd

5 files changed

Lines changed: 167 additions & 78 deletions

File tree

cime_config/create_readnl_files.py

Lines changed: 91 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import argparse
1616
import sys
1717
import logging
18+
import math
1819

1920
# Find and include the ccpp-framework scripts directory
2021
# Assume we are in <CAMROOT>/cime_config and SPIN is in <CAMROOT>/ccpp_framework
@@ -36,6 +37,9 @@
3637
from fortran_tools import FortranWriter
3738
# pylint: enable=wrong-import-position
3839

40+
#Names for possible auto-generated loop variables:
41+
_LOOP_VARS = ["i", "j", "k", "m", "l", "x", "y", "z"]
42+
3943
###############################################################################
4044
def is_int(token):
4145
###############################################################################
@@ -239,6 +243,16 @@ def __init__(self, var_xml):
239243
self.__type = typ
240244
args = self._parse_array_desc(alen, self.var_name)
241245
self.__array_lengths, self.__array_names = args
246+
247+
# Check if there are too many dimensions for the namelist array:
248+
if len(self.array_len) > len(_LOOP_VARS):
249+
emsg = f"Namelist variable '{self.var_name}' has "
250+
emsg += f"{len(self.array_len)} dimensions,\n"
251+
emsg += "which is more than the limit of "
252+
emsg += f"{len(_LOOP_VARS)} dimensions "
253+
emsg += "that is currently supported."
254+
raise IndexError(emsg)
255+
242256
if self.var_type == "character":
243257
# character arguments at least require a length
244258
self.__valid &= self.kind is not None
@@ -648,10 +662,8 @@ def _write_metadata_file(self, nlvars, outdir, logger):
648662
# end for
649663
# end with
650664

651-
def _write_nlread_file(self, nlvars, outdir, log_info,
652-
indent, mpi_obj, logger):
653-
"""Write the namelist reading Fortran module to <outdir>
654-
"""
665+
def _write_nlread_file(self, nlvars, outdir, indent, mpi_obj, logger):
666+
"""Write the namelist reading Fortran module to <outdir>"""
655667
file_desc = f"Module to read namelist variables for {self.scheme}"
656668
# Collect all the kinds used in the file
657669
file_kinds = set()
@@ -666,6 +678,7 @@ def _write_nlread_file(self, nlvars, outdir, log_info,
666678
if logger:
667679
logger.info(f"Writing Fortran module, {self.__nlread_file}")
668680
# end if
681+
669682
with FortranWriter(self.__nlread_file, "w", file_desc,
670683
self.nlread_module, indent=indent) as ofile:
671684
# Write out any kinds needed
@@ -689,10 +702,7 @@ def _write_nlread_file(self, nlvars, outdir, log_info,
689702
ofile.end_module_header()
690703
ofile.blank_line()
691704
# Write out the definition of the namelist reading function
692-
nl_args = ["nl_unit", "mpicomm", "mpiroot", "mpi_isroot"]
693-
if log_info:
694-
nl_args.append("logunit")
695-
# end if
705+
nl_args = ["nl_unit", "mpicomm", "mpiroot", "mpi_isroot", "logunit"]
696706
args = f"({', '.join(nl_args)})"
697707
ofile.write(f"subroutine {self.nlread_func}{args}", 1)
698708
mpi = mpi_obj.mpi_module
@@ -701,6 +711,10 @@ def _write_nlread_file(self, nlvars, outdir, log_info,
701711
ofile.write(f"use {mpi}, {spc}only: {mpi_types}", 2)
702712
spc = ' '*(len("cam_abortutils") - len("shr_nl_mod"))
703713
ofile.write(f"use shr_nl_mod, {spc}only: shr_nl_find_group_name", 2)
714+
spc = ' '*(len("cam_abortutils") - len("cam_logfile"))
715+
ofile.write(f"use cam_logfile, {spc}only: debug_output, DEBUGOUT_INFO", 2)
716+
spc = ' '*(len("cam_abortutils") - len("shr_kind_mod"))
717+
ofile.write(f"use shr_kind_mod, {spc}only: cl=>shr_kind_cl", 2)
704718
ofile.write("use cam_abortutils, only: endrun", 2)
705719
ofile.blank_line()
706720
ofile.comment("Dummy arguments", 2)
@@ -710,12 +724,33 @@ def _write_nlread_file(self, nlvars, outdir, log_info,
710724
ofile.write(f"{comm_type}, intent(in) :: mpicomm", 2)
711725
ofile.write(f"integer,{spc} intent(in) :: mpiroot", 2)
712726
ofile.write(f"logical,{spc} intent(in) :: mpi_isroot", 2)
713-
if log_info:
714-
ofile.write(f"integer,{spc} intent(in) :: logunit", 2)
715-
# end if
727+
ofile.write(f"integer,{spc} intent(in) :: logunit", 2)
716728
ofile.blank_line()
717729
ofile.comment("Local variables", 2)
718730
ofile.write("integer :: ierr", 2)
731+
ofile.write("character(len=cl) :: errmsg", 2)
732+
733+
# Add loop control variables if needed for pretty
734+
# printing array variables to the log file:
735+
#-----------------------------------------
736+
737+
# Determine what the largest number of dimensions
738+
# is for a given variable, as that is how many
739+
# variables we'll need to declare).
740+
max_dims = 0
741+
for grpvars in self.__groups.values():
742+
for grpvar in grpvars:
743+
if grpvar.array_len:
744+
max_dims = max(max_dims, len(grpvar.array_len))
745+
746+
# Write variable declarations needed for looping:
747+
if max_dims > 0:
748+
loop_var_str = ', '.join(_LOOP_VARS[:max_dims])
749+
ofile.blank_line()
750+
ofile.comment("Used for namelist variable logging: ", 2)
751+
ofile.write(f"integer :: {loop_var_str}", 2)
752+
#-----------------------------------------
753+
719754
substr = "character(len=*), parameter :: subname = '{}'"
720755
ofile.write(substr.format(self.nlread_func), 2)
721756
# Declare the namelists
@@ -735,34 +770,54 @@ def _write_nlread_file(self, nlvars, outdir, log_info,
735770
args = f"(nl_unit, '{grpname}', status=ierr)"
736771
ofile.write(f"call shr_nl_find_group_name{args}", 3)
737772
ofile.write("if (ierr == 0) then", 3)
738-
ofile.write(f"read(nl_unit, {grpname}, iostat=ierr)", 4)
773+
ofile.write(f"read(nl_unit, {grpname}, iostat=ierr, iomsg=errmsg)", 4)
739774
ofile.write("if (ierr /= 0) then", 4)
740-
errmsg = f"ERROR reading namelist, {grpname}"
741-
ofile.write(f"call endrun(subname//':: {errmsg}')", 5)
775+
errmsg = f"ERROR reading namelist, {grpname}, with following error: "
776+
ofile.write(f"call endrun(subname//':: {errmsg}'//errmsg)", 5)
742777
ofile.write("end if", 4)
743778
ofile.write("else", 3)
744779
emsg = f"ERROR: Did not find namelist group, {grpname}."
745780
ofile.write(f"call endrun(subname//':: {emsg}')", 4)
746781
ofile.write("end if", 3)
747-
if log_info:
748-
ofile.comment("Print out namelist values", 3)
749-
msg = f"Namelist values from {grpname} for {self.scheme}"
750-
ofile.write(f"write(logunit, *) '{msg}'", 3)
751-
for grpvar in self.__groups[grpname]:
782+
783+
ofile.comment("Print out namelist values", 3)
784+
ofile.write("if (debug_output >= DEBUGOUT_INFO) then", 3)
785+
msg = f"Namelist values from group '{grpname}' for scheme '{self.scheme}'"
786+
ofile.write(f'write(logunit, *) "{msg}"', 4)
787+
for grpvar in self.__groups[grpname]:
788+
if grpvar.array_len:
789+
# do loop syntax:
790+
for dim_count, alen in enumerate(grpvar.array_len):
791+
ofile.write(f"do {_LOOP_VARS[dim_count]}=1,{alen}", 4+dim_count)
792+
793+
# array printing syntax:
794+
loop_var_prt_str = ",',',".join(_LOOP_VARS[:(dim_count+1)])
795+
loop_fmt_str = "a"+",i0,a"*(dim_count+1)
796+
797+
# generate actual Fortran write calls
798+
msg = f"'{grpvar.var_name}(',{loop_var_prt_str},') = '"
799+
ofile.write(f"write(logunit,'({loop_fmt_str})') {msg}", 4+(dim_count+1))
800+
801+
loop_var_str = ','.join(_LOOP_VARS[:(dim_count+1)])
802+
msg = f"{grpvar.var_name}({loop_var_str})"
803+
ofile.write(f"write(logunit, *) {msg}",4+(dim_count+1))
804+
805+
# 'end do' syntax (must be done backwards for correct indenting):
806+
for dim_count in range(len(grpvar.array_len), 0, -1):
807+
ofile.write(f"end do", 4+(dim_count-1))
808+
else:
752809
msg = f"'{grpvar.var_name} = ', {grpvar.var_name}"
753-
ofile.write(f"write(logunit, *) {msg}", 3)
754-
# end for
755-
# end if
756-
ofile.write("end if", 2)
810+
ofile.write(f"write(logunit, *) {msg}", 4)
811+
# end for
812+
ofile.write("end if", 3) #log level check
813+
ofile.write("end if", 2) #root task check
814+
757815
ofile.comment("Broadcast the namelist variables", 2)
758816
for grpvar in self.__groups[grpname]:
759817
arglist = [grpvar.var_name]
760818
dimsize = 1
761819
if grpvar.array_len:
762-
# XXgoldyXX: Can be replaced math.prod in python 3.8+
763-
for alen in grpvar.array_len:
764-
dimsize *= alen
765-
# end for
820+
dimsize = math.prod(grpvar.array_len)
766821
dimstr = f"*{dimsize}"
767822
else:
768823
dimstr = ""
@@ -787,13 +842,12 @@ def _write_nlread_file(self, nlvars, outdir, log_info,
787842
ofile.write(f"end subroutine {self.nlread_func}", 1)
788843
# end with
789844

790-
def process_namelist_def_file(self, outdir, log_info, indent,
791-
mpi_obj, logger):
845+
def process_namelist_def_file(self, outdir, indent, mpi_obj, logger):
792846
"""Read the namelist variables from <nlxml> and produce both a module
793847
with a routine that reads these variables into module variables and
794848
an associated CCPP metadata file.
795849
<outdir> is the directory where the output files are written.
796-
If <log_info> is True, output statments to log namelist values
850+
<indent> is the number of whitespaces used when doing scope indentation
797851
<mpi_obj> is an MpiModuleInfo object used to generate the correct
798852
MPI Fortran statements.
799853
<logger> is a Python logger.
@@ -831,8 +885,7 @@ def process_namelist_def_file(self, outdir, log_info, indent,
831885
self._write_metadata_file(nlvars, outdir, logger)
832886
# end if
833887
if not self.errors:
834-
self._write_nlread_file(nlvars, outdir, log_info,
835-
indent, mpi_obj, logger)
888+
self._write_nlread_file(nlvars, outdir, indent, mpi_obj, logger)
836889
# end if
837890

838891
def group_names(self):
@@ -933,7 +986,6 @@ def __init__(self, args, description, schema_paths=None, logger=None):
933986
# end if
934987
self.__mpi_obj = MpiModuleInfo(args.mpi_comm_arg, args.mpi_root_arg,
935988
args.mpi_is_root_arg, args.mpi_module)
936-
self.__logunit_arg = args.logunit_arg
937989
self.__indent = args.indent
938990
self.__xml_schema_dir = args.xml_schema_dir
939991
self.__namelist_read_mod = args.namelist_read_mod
@@ -976,10 +1028,6 @@ def _parse_command_line(self, args, description):
9761028
parser.add_argument("--mpi-is-root-arg", type=str, default="mpi_isroot",
9771029
help="""Name of the logical communicator root
9781030
(.true. if the current task is root) input argument""")
979-
parser.add_argument("--logunit-arg", type=str, default="logunit",
980-
help="""Name of the output log input argument.
981-
If this argument is not used, namelist schemes
982-
will not log output.""")
9831031
parser.add_argument("--indent", type=int, default=3,
9841032
help="Indent level for Fortran source code")
9851033
parser.add_argument("--xml-schema-dir", type=str, default=_XML_SCHEMAS,
@@ -1093,10 +1141,7 @@ def _write_active_scheme_reader(self, outdir, logger):
10931141
# Standard arguments
10941142
arglist = [self.nlfile_arg, self.active_schemes_arg,
10951143
self.mpi_comm_arg, self.mpi_root_arg,
1096-
self.mpi_is_root_arg]
1097-
if self.log_values():
1098-
arglist.append(self.logunit_arg)
1099-
# end if
1144+
self.mpi_is_root_arg, "logunit"]
11001145
# Host-level namelist parameters
11011146
#XXgoldyXX: Need a process for this
11021147
args = ", ".join(arglist)
@@ -1125,9 +1170,7 @@ def _write_active_scheme_reader(self, outdir, logger):
11251170
ofile.write(f"{int_input} :: {self.mpi_comm_arg}", 2)
11261171
ofile.write(f"{int_input} :: {self.mpi_root_arg}", 2)
11271172
ofile.write(f"{logical_input} :: {self.mpi_is_root_arg}", 2)
1128-
if self.log_values():
1129-
ofile.write(f"{int_input} :: {self.logunit_arg}", 2)
1130-
# end if
1173+
ofile.write(f"{int_input} :: logunit", 2)
11311174
ofile.blank_line()
11321175
ofile.comment("Local variable", 2)
11331176
ofile.write("integer :: nl_unit", 2)
@@ -1136,10 +1179,8 @@ def _write_active_scheme_reader(self, outdir, logger):
11361179
open_args = f"newunit=nl_unit, file=trim({self.nlfile_arg})"
11371180
ofile.write(f"open({open_args}, status='old')", 2)
11381181
nlarglist = ["nl_unit", self.mpi_comm_arg,
1139-
self.mpi_root_arg, self.mpi_is_root_arg]
1140-
if self.log_values():
1141-
nlarglist.append(self.logunit_arg)
1142-
# end if
1182+
self.mpi_root_arg, self.mpi_is_root_arg,
1183+
"logunit"]
11431184
nlargs = ", ".join(nlarglist)
11441185
for scheme_name in self.schemes():
11451186
func_name = self.__scheme_nl_files[scheme_name].nlread_func
@@ -1168,8 +1209,8 @@ def write_nl_files(self, outdir, logger):
11681209
errors = []
11691210
for scheme_name in self.schemes():
11701211
scheme = self.__scheme_nl_files[scheme_name]
1171-
scheme.process_namelist_def_file(outdir, self.log_values(),
1172-
self.indent, self.mpi_obj, logger)
1212+
scheme.process_namelist_def_file(outdir, self.indent,
1213+
self.mpi_obj, logger)
11731214
if scheme.errors:
11741215
errors.extend(scheme.errors)
11751216
# endif
@@ -1228,10 +1269,6 @@ def groups(self, schemes=None):
12281269
# end if
12291270
return sorted(groups)
12301271

1231-
def log_values(self):
1232-
"""Return True if namelist values should be written"""
1233-
return isinstance(self.__logunit_arg, str) and self.__logunit_arg
1234-
12351272
@property
12361273
def nlfile_arg(self):
12371274
"""Return the name of the input namelist file argument"""
@@ -1289,13 +1326,6 @@ def namelist_read_subname(self):
12891326
"""
12901327
return self.__namelist_read_subname
12911328

1292-
@property
1293-
def logunit_arg(self):
1294-
"""Return the dummy argument name for write statements used to log
1295-
namelist variable values.
1296-
"""
1297-
return self.__logunit_arg
1298-
12991329
@property
13001330
def indent(self):
13011331
"""Return the indent unit size (in spaces) for this NamelistFiles object

src/control/runtime_opts.F90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ subroutine read_namelist(nlfilename, single_column, scmlat, scmlon)
8686
! Note that namelists for physics schemes are read by
8787
! cam_read_ccpp_scheme_namelists
8888

89-
call cam_logfile_readnl(nlfilename)
89+
call cam_logfile_readnl(nlfilename) !The log settings must always be read first
9090
! call physics_grid_readnl(nlfilename)
9191
call physconst_readnl(nlfilename)
9292
call cam_initfiles_readnl(nlfilename)

test/unit/python/sample_files/namelist_files/banana_namelist.F90

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ module banana_namelist
3636
subroutine autogen_banana_readnl(nl_unit, mpicomm, mpiroot, mpi_isroot, logunit)
3737
use mpi, only: MPI_Integer, MPI_Real8
3838
use shr_nl_mod, only: shr_nl_find_group_name
39+
use cam_logfile, only: debug_output, DEBUGOUT_INFO
40+
use shr_kind_mod, only: cl=>shr_kind_cl
3941
use cam_abortutils, only: endrun
4042

4143
! Dummy arguments
@@ -47,6 +49,7 @@ subroutine autogen_banana_readnl(nl_unit, mpicomm, mpiroot, mpi_isroot, logunit)
4749

4850
! Local variables
4951
integer :: ierr
52+
character(len=cl) :: errmsg
5053
character(len=*), parameter :: subname = 'autogen_banana_readnl'
5154

5255
namelist /banana_nl/ rayk0, raykrange, raytau0
@@ -56,18 +59,22 @@ subroutine autogen_banana_readnl(nl_unit, mpicomm, mpiroot, mpi_isroot, logunit)
5659
rewind(nl_unit)
5760
call shr_nl_find_group_name(nl_unit, 'banana_nl', status=ierr)
5861
if (ierr == 0) then
59-
read(nl_unit, banana_nl, iostat=ierr)
62+
read(nl_unit, banana_nl, iostat=ierr, iomsg=errmsg)
6063
if (ierr /= 0) then
61-
call endrun(subname//':: ERROR reading namelist, banana_nl')
64+
call &
65+
endrun(subname// &
66+
':: ERROR reading namelist, banana_nl, with following error: '//errmsg)
6267
end if
6368
else
6469
call endrun(subname//':: ERROR: Did not find namelist group, banana_nl.')
6570
end if
6671
! Print out namelist values
67-
write(logunit, *) 'Namelist values from banana_nl for banana'
68-
write(logunit, *) 'rayk0 = ', rayk0
69-
write(logunit, *) 'raykrange = ', raykrange
70-
write(logunit, *) 'raytau0 = ', raytau0
72+
if (debug_output >= DEBUGOUT_INFO) then
73+
write(logunit, *) "Namelist values from group 'banana_nl' for scheme 'banana'"
74+
write(logunit, *) 'rayk0 = ', rayk0
75+
write(logunit, *) 'raykrange = ', raykrange
76+
write(logunit, *) 'raytau0 = ', raytau0
77+
end if
7178
end if
7279
! Broadcast the namelist variables
7380
call mpi_bcast(rayk0, 1, MPI_Integer, mpiroot, mpicomm, ierr)

0 commit comments

Comments
 (0)