diff --git a/.gitmodules b/.gitmodules index 608e17c73..0674fce9b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,9 @@ fxtag = pio2_6_6 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NCAR/ParallelIO + +[submodule "toml-f"] +path = externals/toml-f +url = https://github.com/toml-f/toml-f.git +fxtag = v0.5.2 +fxrequired = Required diff --git a/cime_config/buildlib b/cime_config/buildlib index 459106d26..2d608edae 100755 --- a/cime_config/buildlib +++ b/cime_config/buildlib @@ -3,7 +3,7 @@ """ build mizuRoute library """ -import sys, os +import sys, os, glob, shutil _CIMEROOT = os.environ.get("CIMEROOT") if _CIMEROOT is None: @@ -36,7 +36,70 @@ def _main_func(): expect( driver == "nuopc", "mizuRoute only has a nuopc COMP_INTERFACE" ) #------------------------------------------------------- - # create Filepath file + # Pre-build external toml-f static library via CMake + #------------------------------------------------------- + tomlf_lib = os.path.join(libroot, "libtomlf.a") + tomlf_bld = os.path.join(bldroot, "tomlf_bld") + tomlf_inst = os.path.join(bldroot, "tomlf_inst") + + if not os.path.isfile(tomlf_lib): + incroot = case.get_value("INCROOT") + srcroot = case.get_value("SRCROOT") + + tomlf_src = os.path.join(rof_root, "externals", "toml-f") + if not os.path.exists(os.path.join(tomlf_src, "CMakeLists.txt")): + tomlf_src = os.path.join(rof_root, "..", "externals", "toml-f") + if not os.path.exists(os.path.join(tomlf_src, "CMakeLists.txt")): + tomlf_src = os.path.join(srcroot, "externals", "toml-f") + + expect(os.path.exists(os.path.join(tomlf_src, "CMakeLists.txt")), + "Could not find toml-f CMakeLists.txt at %s" % tomlf_src) + + if not os.path.isdir(tomlf_bld): + os.makedirs(tomlf_bld) + + fc = os.environ.get("FC") or os.environ.get("MPIFC") or os.environ.get("SFC") + fflags = os.environ.get("FFLAGS", "") + macfile = os.path.join(caseroot, "Macros.make") + if os.path.isfile(macfile): + with open(macfile, "r") as mf: + for line in mf: + sline = line.strip() + if sline.startswith("MPIFC :=") and not fc: + fc = sline.split(":=")[-1].strip() + elif sline.startswith("SFC :=") and not fc: + fc = sline.split(":=")[-1].strip() + elif sline.startswith("FFLAGS :=") and not fflags: + fflags = sline.split(":=")[-1].strip() + if not fc: + fc = "ftn" + + if not os.path.isfile(os.path.join(tomlf_bld, "Makefile")): + cmake_cmd = 'cmake -DCMAKE_Fortran_COMPILER="{}" -DCMAKE_Fortran_FLAGS="{}" -DCMAKE_INSTALL_PREFIX="{}" "{}"'.format( + fc, fflags, tomlf_inst, tomlf_src + ) + rc_cmake, out_cmake, err_cmake = run_cmd(cmake_cmd, from_dir=tomlf_bld) + logger.info("CMake configure toml-f:\n output:\n%s\n err:\n%s\n" % (out_cmake, err_cmake)) + expect(rc_cmake == 0, "CMake configure for toml-f failed with rc=%s:\n%s" % (rc_cmake, err_cmake)) + + make_cmd = "{} -j {} && {} install".format(gmake, gmake_j, gmake) + rc_make, out_make, err_make = run_cmd(make_cmd, from_dir=tomlf_bld) + logger.info("Make toml-f:\n output:\n%s\n err:\n%s\n" % (out_make, err_make)) + expect(rc_make == 0, "Make for toml-f failed with rc=%s:\n%s" % (rc_make, err_make)) + + mod_files = glob.glob(os.path.join(tomlf_bld, "**", "*.mod"), recursive=True) + \ + glob.glob(os.path.join(tomlf_inst, "**", "*.mod"), recursive=True) + for mod in mod_files: + shutil.copy(mod, incroot) + shutil.copy(mod, libroot) + + compiled_libs = glob.glob(os.path.join(tomlf_bld, "**", "libtoml*.a"), recursive=True) + \ + glob.glob(os.path.join(tomlf_inst, "**", "libtoml*.a"), recursive=True) + expect(len(compiled_libs) > 0, "Could not find compiled toml-f library in %s or %s" % (tomlf_bld, tomlf_inst)) + shutil.copy(compiled_libs[0], tomlf_lib) + + #------------------------------------------------------- + # create Filepath file for mizuRoute #------------------------------------------------------- filepath_file = os.path.join(bldroot,"Filepath") if not os.path.isfile(filepath_file): @@ -63,6 +126,23 @@ def _main_func(): logger.info("%s: \n\n output:\n %s \n\n err:\n\n%s\n"%(cmd,out,err)) expect(rc == 0, "Command %s failed with rc=%s" % (cmd, rc)) + #------------------------------------------------------- + # Merge libtomlf.a object files into librof.a via MRI script + #------------------------------------------------------- + ar = case.get_value("AR") or "ar" + mri_script = os.path.join(tomlf_bld, "merge.mri") + with open(mri_script, "w") as f: + f.write("CREATE {}\n".format(complib)) + f.write("ADDLIB {}\n".format(complib)) + f.write("ADDLIB {}\n".format(tomlf_lib)) + f.write("SAVE\n") + f.write("END\n") + + ar_cmd = "{} -M < {}".format(ar, mri_script) + rc_ar, out_ar, err_ar = run_cmd(ar_cmd) + logger.info("Merging libtomlf.a into librof.a via MRI script:\n output:\n%s\n err:\n%s\n" % (out_ar, err_ar)) + expect(rc_ar == 0, "Merge of libtomlf.a into librof.a failed with rc=%s:\n%s" % (rc_ar, err_ar)) + ############################################################################### if __name__ == "__main__": diff --git a/cime_config/buildnml b/cime_config/buildnml index 1b09150ef..0a3e6ba78 100755 --- a/cime_config/buildnml +++ b/cime_config/buildnml @@ -242,25 +242,51 @@ def _create_control_files(case, caseroot, srcroot, confdir, inst_string, infile, fname_state_in = "empty" ctl.set( "fname_state_in", fname_state_in ) - if fname_state_in is not "empty": + if fname_state_in != "empty": nmlgen.set_value( "fname_state_in", value=os.path.join( ancil_dir, fname_ntopOld ) ) - # Read in the user control file for the case and change settings to it - file_src = "user_nl_mizuroute_control" - user_ctl_file = os.path.join(caseroot, file_src + inst_string) - if ( not os.path.exists( user_ctl_file ) ): - safe_copy( os.path.join( srcroot, "cime_config", file_src), user_ctl_file ) - usrctl = mizuRoute_control() - usrctl.read( user_ctl_file, allowEmpty=True ) - for element in usrctl.get_elmList(): - value = ctl.get( element ) - expect( value != "UNSET", "Element in the user_nl_mizuroute_control file is NOT in the control file: "+element ) - ctl.set( element, usrctl.get( element ) ) + # Check component defaults file in cime_config + default_file_toml = os.path.join(srcroot, "cime_config", "user_nl_mizuroute_toml") + default_file_control = os.path.join(srcroot, "cime_config", "user_nl_mizuroute_control") + default_file_plain = os.path.join(srcroot, "cime_config", "user_nl_mizuroute") + + has_def_toml = os.path.exists(default_file_toml) or os.path.exists(default_file_plain) + has_def_control = os.path.exists(default_file_control) + + expect(not (has_def_toml and has_def_control), + "Both TOML and control format default template files exist in cime_config. Exactly one must be present.") + expect(has_def_toml or has_def_control, + "Neither user_nl_mizuroute_toml nor user_nl_mizuroute_control default file exists in cime_config.") + + # Check user override files in caseroot + user_file_toml = os.path.join(caseroot, "user_nl_mizuroute_toml" + inst_string) + user_file_control = os.path.join(caseroot, "user_nl_mizuroute_control" + inst_string) + user_file_plain = os.path.join(caseroot, "user_nl_mizuroute" + inst_string) + + has_user_toml = os.path.exists(user_file_toml) + has_user_control = os.path.exists(user_file_control) + + expect(not (has_user_toml and has_user_control), + "Both TOML and control format user override files exist in CASEROOT. Please use only one format.") + + usrctl = None + if has_user_toml: + usrctl = mizuRoute_control.from_toml( user_file_toml, allowEmpty=True ) + elif has_user_control: + usrctl = mizuRoute_control.from_control( user_file_control, allowEmpty=True ) + elif os.path.exists(user_file_plain): + usrctl = mizuRoute_control.from_toml( user_file_plain, allowEmpty=True ) + + if usrctl is not None: + for element in usrctl.get_elmList(): + value = usrctl.get( element ) + expect( ctl.get(element) != "UNSET", "Element in the user control file is NOT in the template control file: "+element ) + ctl.set( element, value ) #---------------------------------------------------- # Write output files #---------------------------------------------------- - control_file = os.path.join(confdir, "mizuRoute.control") + control_file = os.path.join(confdir, "mizuroute_toml") nml_file = os.path.join(confdir, "mizuRoute_in") write_nml_in_file(case, nmlgen, confdir, nml_file ) ctl.write( control_file ) @@ -296,9 +322,8 @@ def buildnml(case, caseroot, compname): #---------------------------------------------------- # Construct the control file generator #---------------------------------------------------- - sampleFile = srcroot + "/route/settings/SAMPLE-coupled.control" - ctl = mizuRoute_control() - ctl.read( sampleFile ) + sampleFile = srcroot + "/route/settings/SAMPLE-coupled_toml" + ctl = mizuRoute_control.from_toml( sampleFile ) #---------------------------------------------------- # Do some checking @@ -334,7 +359,7 @@ def buildnml(case, caseroot, compname): # copy control files to rundir if os.path.isdir(rundir): for destdir in [rundir, caseroot+"/CaseDocs"]: - for nfile in ["mizuRoute.control", "mizuRoute_in" ]: + for nfile in ["mizuroute_toml", "mizuRoute_in" ]: file_src = os.path.join(confdir, nfile ) file_dest = os.path.join(destdir, nfile ) if inst_string: diff --git a/cime_config/test/runbuildnml b/cime_config/test/runbuildnml index f5d4542ab..6b7d63c90 100755 --- a/cime_config/test/runbuildnml +++ b/cime_config/test/runbuildnml @@ -1,5 +1,6 @@ #!/bin/bash -# Run the buildnmal for mizuRoute, assing it's under a CTSM or CESM checkout +# Run buildnml for mizuRoute under a CTSM or CESM checkout + cd ../../../../cime >& /dev/null if [ $? != 0 ]; then echo "cime directory does not exist where expected" @@ -9,24 +10,48 @@ export CIMEROOT=`pwd` echo "CIMEROOT = $CIMEROOT" cd - -cp ../user_nl_* . -mkdir CaseDocs -echo "Run the help option" +mkdir -p CaseDocs + +echo "1. Run the help option" ../buildnml --help > /dev/null if [ $? != 0 ] ; then - echo "test FAIL" + echo "help test FAIL" exit -1 fi -echo "Try a simple test" + +echo "2. Test TOML user control file (user_nl_mizuroute_toml)" +rm -rf user_* Buildconf/mizurouteconf/* CaseDocs/* +touch user_nl_mizuroute +cp ../user_nl_mizuroute_toml user_nl_mizuroute_toml +echo "newFileFrequency = \"monthly\"" >> user_nl_mizuroute_toml ../buildnml `pwd` --verbose if [ $? != 0 ] ; then - echo "test FAIL" + echo "TOML test FAIL" + exit -1 +fi + +echo "3. Test Legacy user control file (user_nl_mizuroute_control)" +rm -rf user_* Buildconf/mizurouteconf/* CaseDocs/* +touch user_nl_mizuroute +echo " monthly ! Test comment" > user_nl_mizuroute_control +../buildnml `pwd` --verbose +if [ $? != 0 ] ; then + echo "Legacy control test FAIL" + exit -1 +fi + +echo "4. Test Dual-Format conflict detection (expect failure when both exist)" +rm -rf user_* Buildconf/mizurouteconf/* CaseDocs/* +touch user_nl_mizuroute +cp ../user_nl_mizuroute_toml user_nl_mizuroute_toml +echo " monthly ! Test comment" > user_nl_mizuroute_control +../buildnml `pwd` --verbose >& /dev/null +if [ $? == 0 ] ; then + echo "Dual-format conflict test FAIL (should have thrown error when both formats exist)" exit -1 else - echo "Cat the results...." - cat Buildconf/mizurouteconf/mizuRoute* - echo "input_data_list..." - cat Buildconf/mizuroute.input_data_list + echo "Dual-format conflict test PASSED (correctly rejected ambiguous dual formats)" fi + rm -rf user_* run/* Buildconf/mizurouteconf/* Buildconf/* CaseDocs -echo "Successfully ran test" +echo "Successfully ran all buildnml tests" diff --git a/cime_config/user_nl_mizuroute_control b/cime_config/user_nl_mizuroute_control deleted file mode 100644 index 379da460d..000000000 --- a/cime_config/user_nl_mizuroute_control +++ /dev/null @@ -1,14 +0,0 @@ -!---------------------------------------------------------------------------------- -! This is for changes to the mizuRoute control file only -! Changes to the namelist file need to go in the user_nl_mizuRoute file -! -! THIS FILE MUST BE IN mizuRoute CONTROL FILE FORMAT -! See ../route/settings/SAMPLE-coupled.control -! -! Here are some examples (remove the leading bang): -! -! monthly ! Ending comment -! 2 ! Ending comment -! 1 ! Ending comment -! -!---------------------------------------------------------------------------------- diff --git a/cime_config/user_nl_mizuroute_toml b/cime_config/user_nl_mizuroute_toml new file mode 100644 index 000000000..c09530034 --- /dev/null +++ b/cime_config/user_nl_mizuroute_toml @@ -0,0 +1,13 @@ +#---------------------------------------------------------------------------------- +# This is for changes to the mizuRoute control file only +# Changes to the namelist file need to go in the user_nl_mizuRoute file +# +# See ../route/settings/SAMPLE-coupled_toml +# +# Here are some examples (remove the leading #): +# +# newFileFrequency = "monthly" # Ending comment +# route_opt = 2 # Ending comment +# doesAccumRunoff = 1 # Ending comment +# +#---------------------------------------------------------------------------------- diff --git a/route/build/Makefile b/route/build/Makefile index d47a10f31..c89f4d4c7 100644 --- a/route/build/Makefile +++ b/route/build/Makefile @@ -155,8 +155,8 @@ MOD_PATH = $(F_MASTER)build/ EXE_PATH = $(F_MASTER)bin # External libraries (if used) -EXTLIBS = -EXTINCLUDES = +EXTLIBS = $(F_MASTER)../externals/toml-f/_install/lib/libtoml-f.a +EXTINCLUDES = -I$(F_MASTER)../externals/toml-f/_install/include/toml-f/modules #======================================================================== # Assemble all of the sub-routines @@ -274,6 +274,14 @@ ifdef PNETCDF_PATH LDFLAGS += -L$(PNETCDF_PATH)/lib -lpnetcdf endif +TOMLLIBDIR = $(F_MASTER)../externals/toml-f/_install + +$(TOMLLIBDIR)/lib/libtoml-f.a: + @mkdir -p $(F_MASTER)../externals/toml-f/_build + cd $(F_MASTER)../externals/toml-f/_build && \ + cmake -DCMAKE_Fortran_COMPILER=$(FC_EXE) -DCMAKE_INSTALL_PREFIX=$(TOMLLIBDIR) .. && \ + $(MAKE) && $(MAKE) install + $(PIOLIB): cd $(LIBDIR); \ $(MAKE) $(MFLAGS) F_MASTER=$(F_MASTER) FC=$(FC_EXE) FC_EXE=$(FC_EXE) FLAGS="$(FLAGS)" \ diff --git a/route/build/cpl/RtmVar.F90 b/route/build/cpl/RtmVar.F90 index 431445be2..3d6b7bea9 100644 --- a/route/build/cpl/RtmVar.F90 +++ b/route/build/cpl/RtmVar.F90 @@ -40,7 +40,7 @@ MODULE RtmVar integer, public :: rtmhist_ndens = 1 ! namelist: output density of netcdf history files integer, public :: rtmhist_mfilt = 30 ! namelist: number of time samples per tape integer, public :: rtmhist_nhtfrq = 0 ! namelist: history write freq(0=monthly) - character(len=256),public :: cfile_name = 'mizuRoute.control' + character(len=256),public :: cfile_name = 'mizuroute_toml' character(len=256),public :: para_xxxx = 'mizuRoute_in' ! Miscellaneous variables logical, public :: barrier_timers = .false. ! barrier timers diff --git a/route/build/lib/Makefile b/route/build/lib/Makefile index ad6259974..fd9ba620b 100644 --- a/route/build/lib/Makefile +++ b/route/build/lib/Makefile @@ -91,6 +91,7 @@ cleanpiolib: .PHONY : cleanpiolib $(PIOLIBMAKE): + mkdir -p $(PIOLIBDIR); \ cd $(PIOLIBDIR); \ $(CMAKE_ENV_VARS) cmake $(CMAKE_OPTS) $(MODEARGS) $(PIO2DIR) diff --git a/route/build/src/read_control.f90 b/route/build/src/read_control.f90 index 97f34c516..66794ff63 100644 --- a/route/build/src/read_control.f90 +++ b/route/build/src/read_control.f90 @@ -4,91 +4,321 @@ MODULE read_control_module USE nrtype USE public_var +USE tomlf implicit none +INTERFACE get_toml_val + MODULE PROCEDURE get_toml_val_char + MODULE PROCEDURE get_toml_val_int + MODULE PROCEDURE get_toml_val_dp + MODULE PROCEDURE get_toml_val_sp + MODULE PROCEDURE get_toml_val_bool +END INTERFACE get_toml_val + private public::read_control +public::read_control_toml +public::read_control_legacy CONTAINS ! ======================================================================================================= - ! public subroutine: read the control file + ! public subroutine: read the control file (generic dispatcher based on file suffix) ! ======================================================================================================= SUBROUTINE read_control(ctl_fname, err, message) + USE ascii_utils, ONLY: lower ! convert string to lower case - ! global vars - USE globalData, ONLY: time_conv,length_conv ! conversion factors - USE globalData, ONLY: time_conv_solute ! time conversion factor for solute mass - USE globalData, ONLY: mass_conv_solute ! mass conversion factors - USE globalData, ONLY: masterproc ! procs id and number of procs - ! metadata structures - USE globalData, ONLY: meta_HRU ! HRU properties - USE globalData, ONLY: meta_HRU2SEG ! HRU-to-segment mapping - USE globalData, ONLY: meta_SEG ! stream segment properties - USE globalData, ONLY: meta_NTOPO ! network topology - USE globalData, ONLY: meta_PFAF ! pfafstetter code - USE globalData, ONLY: meta_rflx ! river flux variables - USE globalData, ONLY: meta_hflx ! river flux variables - USE globalData, ONLY: isColdStart ! initial river state - cold start (T) or from restart file (F) - USE globalData, ONLY: nRoutes ! number of active routing methods - USE globalData, ONLY: routeMethods ! active routing method index and id - USE globalData, ONLY: onRoute ! logical to indicate actiive routing method(s) - USE globalData, ONLY: idxSUM,idxIRF,idxKWT, & - idxKW,idxMC,idxDW - USE globalData, ONLY: runMode ! mizuRoute run mode: standalone, cesm-coupling - ! index of named variables in each structure - USE var_lookup, ONLY: ixHRU - USE var_lookup, ONLY: ixHRU2SEG - USE var_lookup, ONLY: ixSEG - USE var_lookup, ONLY: ixNTOPO - USE var_lookup, ONLY: ixPFAF - USE var_lookup, ONLY: ixRFLX - USE var_lookup, ONLY: ixHFLX - ! external subroutines - USE ascii_utils, ONLY: file_open ! open file (performs a few checks as well) - USE ascii_utils, ONLY: get_vlines ! get a list of character strings from non-comment lines - USE ascii_utils, ONLY: lower ! convert string to lower case - USE nr_utils, ONLY: char2int ! convert integer number to a array containing individual digits - - implicit none - ! argument variables - character(*), intent(in) :: ctl_fname ! name of the control file - integer(i4b),intent(out) :: err ! error code - character(*),intent(out) :: message ! error message - ! Local variables - character(len=strLen),allocatable :: cLines(:) ! vector of character strings - character(len=strLen) :: cName,cData ! name and data from cLines(iLine) - character(len=strLen) :: cLength,cTime ! length and time units - character(len=strLen) :: cMass ! mass units needed only when tracer is on - logical(lgt) :: isGeneric ! temporal logical scalar - logical(lgt) :: onlyOneRouting ! temporal logical scalar - integer(i4b) :: ipos ! index of character string - integer(i4b) :: ibeg_name ! start index of variable name in string cLines(iLine) - integer(i4b) :: iend_name ! end index of variable name in string cLines(iLine) - integer(i4b) :: iend_data ! end index of data in string cLines(iLine) - integer(i4b) :: iLine ! index of line in cLines - integer(i4b) :: iunit ! file unit - integer(i4b) :: io_error ! error in I/O - integer(i4b) :: iRoute ! loop index - character(len=strLen) :: cmessage ! error message from subroutine - - err=0; message='read_control/' - - ! *** get a list of character strings from non-comment lines **** - ! open file (also returns un-used file unit used to open the file) - call file_open(trim(ctl_fname),iunit,err,cmessage) - if(err/=0)then; message=trim(message)//trim(cmessage);return;endif - - ! get a list of character strings from non-comment lines - call get_vlines(iunit,cLines,err,cmessage) - if(err/=0)then; message=trim(message)//trim(cmessage);return;endif - - close(iunit) + implicit none + ! argument variables + character(*), intent(in) :: ctl_fname ! name of the control file + integer(i4b),intent(out) :: err ! error code + character(*),intent(out) :: message ! error message + ! Local variables + character(len=strLen) :: cmessage ! error message of downwind routine + logical(lgt) :: is_toml ! true if control file is in TOML format - if (masterproc) then - write(iulog,'(2a)') new_line('a'), '---- read control file --- ' - end if + err=0; message='read_control/' + + is_toml = .false. + if (len_trim(ctl_fname) >= 5) then + if (lower(ctl_fname(len_trim(ctl_fname)-4:len_trim(ctl_fname))) == '_toml' .or. & + lower(ctl_fname(len_trim(ctl_fname)-4:len_trim(ctl_fname))) == '.toml') then + is_toml = .true. + endif + endif + + if (is_toml) then + call read_control_toml(ctl_fname, err, cmessage) + else + call read_control_legacy(ctl_fname, err, cmessage) + endif + if (err /= 0) message = trim(message) // trim(cmessage) + + END SUBROUTINE read_control + + ! ======================================================================================================= + ! public subroutine: read TOML format control file + ! ======================================================================================================= + SUBROUTINE read_control_toml(ctl_fname, err, message) + ! global vars + USE globalData + USE var_lookup + + implicit none + ! argument variables + character(*), intent(in) :: ctl_fname ! name of the control file + integer(i4b),intent(out) :: err ! error code + character(*),intent(out) :: message ! error message + ! Local variables + type(toml_table), allocatable :: table ! TOML root table structure + type(toml_error), allocatable :: error ! TOML parser error + type(toml_key), allocatable :: keys(:) ! list of keys in TOML table + integer(i4b) :: iKey ! loop index + + err=0; message='read_control_toml/' + + if (masterproc) then + write(iulog,'(2a)') new_line('a'), '---- read TOML control file --- ' + end if + + ! Open and parse TOML control file using toml-f library + call toml_load(table, trim(ctl_fname), error=error) + + if (allocated(error)) then + err = 20 + message = trim(message) // 'failed to parse TOML control file: ' // trim(error%message) + return + endif + + ! Validate keys in TOML control file against known control variables + call table%get_keys(keys) + if (allocated(keys)) then + do iKey = 1, size(keys) + if (.not. is_valid_control_key(keys(iKey)%key)) then + err = 20 + message = trim(message) // 'unexpected variable in TOML control file: ' // trim(keys(iKey)%key) + return + endif + end do + endif + + ! Extract variables from TOML table using toml-f interface + call get_toml_val(table, "ancil_dir", ancil_dir) + call get_toml_val(table, "input_dir", input_dir) + call get_toml_val(table, "output_dir", output_dir) + call get_toml_val(table, "restart_dir", restart_dir) + call get_toml_val(table, "case_name", case_name) + call get_toml_val(table, "sim_start", simStart) + call get_toml_val(table, "sim_end", simEnd) + call get_toml_val(table, "continue_run", continue_run) + call get_toml_val(table, "route_opt", routOpt) + call get_toml_val(table, "doesBasinRoute", doesBasinRoute) + call get_toml_val(table, "dt_qsim", dt) + call get_toml_val(table, "floodplain", floodplain) + call get_toml_val(table, "hw_drain_point", hw_drain_point) + call get_toml_val(table, "tracer", tracer) + call get_toml_val(table, "is_lake_sim", is_lake_sim) + call get_toml_val(table, "lakeRegulate", lakeRegulate) + call get_toml_val(table, "LakeInputOption", LakeInputOption) + call get_toml_val(table, "is_flux_wm", is_flux_wm) + call get_toml_val(table, "is_vol_wm", is_vol_wm) + call get_toml_val(table, "is_vol_wm_jumpstart", is_vol_wm_jumpstart) + call get_toml_val(table, "scale_factor_runoff", scale_factor_runoff) + call get_toml_val(table, "offset_value_runoff", offset_value_runoff) + call get_toml_val(table, "scale_factor_Ep", scale_factor_Ep) + call get_toml_val(table, "offset_value_Ep", offset_value_Ep) + call get_toml_val(table, "is_Ep_upward_negative", is_Ep_upward_negative) + call get_toml_val(table, "scale_factor_prec", scale_factor_prec) + call get_toml_val(table, "offset_value_prec", offset_value_prec) + call get_toml_val(table, "min_length_route", min_length_route) + call get_toml_val(table, "fname_ntopOld", fname_ntopOld) + call get_toml_val(table, "ntopAugmentMode", ntopAugmentMode) + call get_toml_val(table, "fname_ntopNew", fname_ntopNew) + call get_toml_val(table, "dname_nhru", dname_nhru) + call get_toml_val(table, "dname_sseg", dname_sseg) + call get_toml_val(table, "fname_qsim", fname_qsim) + call get_toml_val(table, "vname_qsim", vname_qsim) + call get_toml_val(table, "vname_evapo", vname_evapo) + call get_toml_val(table, "vname_precip", vname_precip) + call get_toml_val(table, "vname_solute", vname_solute) + call get_toml_val(table, "vname_time", vname_time) + call get_toml_val(table, "vname_hruid", vname_hruid) + call get_toml_val(table, "dname_time", dname_time) + call get_toml_val(table, "dname_hruid", dname_hruid) + call get_toml_val(table, "dname_xlon", dname_xlon) + call get_toml_val(table, "dname_ylat", dname_ylat) + call get_toml_val(table, "units_qsim", units_qsim) + call get_toml_val(table, "units_cc", units_cc) + call get_toml_val(table, "dt_ro", dt_ro) + call get_toml_val(table, "input_fillvalue", input_fillvalue) + call get_toml_val(table, "ro_calendar", ro_calendar) + call get_toml_val(table, "ro_time_units", ro_time_units) + call get_toml_val(table, "ro_time_stamp", ro_time_stamp) + call get_toml_val(table, "runoffMin", runoffMin) + call get_toml_val(table, "fname_wm", fname_wm) + call get_toml_val(table, "vname_flux_wm", vname_flux_wm) + call get_toml_val(table, "vname_vol_wm", vname_vol_wm) + call get_toml_val(table, "vname_time_wm", vname_time_wm) + call get_toml_val(table, "vname_segid_wm", vname_segid_wm) + call get_toml_val(table, "dname_time_wm", dname_time_wm) + call get_toml_val(table, "dname_segid_wm", dname_segid_wm) + call get_toml_val(table, "dt_wm", dt_wm) + call get_toml_val(table, "is_remap", is_remap) + call get_toml_val(table, "fname_remap", fname_remap) + call get_toml_val(table, "vname_hruid_in_remap", vname_hruid_in_remap) + call get_toml_val(table, "vname_weight", vname_weight) + call get_toml_val(table, "vname_qhruid", vname_qhruid) + call get_toml_val(table, "vname_num_qhru", vname_num_qhru) + call get_toml_val(table, "vname_i_index", vname_i_index) + call get_toml_val(table, "vname_j_index", vname_j_index) + call get_toml_val(table, "dname_hru_remap", dname_hru_remap) + call get_toml_val(table, "dname_data_remap", dname_data_remap) + call get_toml_val(table, "restart_write", restart_write) + call get_toml_val(table, "restart_date", restart_date) + call get_toml_val(table, "restart_month", restart_month) + call get_toml_val(table, "restart_day", restart_day) + call get_toml_val(table, "restart_hour", restart_hour) + call get_toml_val(table, "fname_state_in", fname_state_in) + call get_toml_val(table, "param_nml", param_nml) + call get_toml_val(table, "qmodOption", qmodOption) + call get_toml_val(table, "qBlendPeriod", qBlendPeriod) + call get_toml_val(table, "QerrTrend", QerrTrend) + call get_toml_val(table, "hydGeometryOption", hydGeometryOption) + call get_toml_val(table, "topoNetworkOption", topoNetworkOption) + call get_toml_val(table, "computeReachList", computeReachList) + call get_toml_val(table, "gageMetaFile", gageMetaFile) + call get_toml_val(table, "outputAtGage", outputAtGage) + call get_toml_val(table, "fname_gageObs", fname_gageObs) + call get_toml_val(table, "vname_gageFlow", vname_gageFlow) + call get_toml_val(table, "vname_gageSite", vname_gageSite) + call get_toml_val(table, "vname_gageTime", vname_gageTime) + call get_toml_val(table, "dname_gageSite", dname_gageSite) + call get_toml_val(table, "dname_gageTime", dname_gageTime) + call get_toml_val(table, "strlen_gageSite", strlen_gageSite) + call get_toml_val(table, "pio_netcdf_format", pio_netcdf_format) + call get_toml_val(table, "pio_netcdf_type", pio_typename) + call get_toml_val(table, "debug", debug) + call get_toml_val(table, "seg_outlet", idSegOut) + call get_toml_val(table, "desireId", desireId) + call get_toml_val(table, "checkMassBalance", checkMassBalance) + call get_toml_val(table, "maxPfafLen", maxPfafLen) + call get_toml_val(table, "pfafMissing", pfafMissing) + call get_toml_val(table, "time_units", time_units) + call get_toml_val(table, "newFileFrequency", newFileFrequency) + call get_toml_val(table, "outputFrequency", outputFrequency) + call get_toml_val(table, "outputNameOption", outputNameOption) + call get_toml_val(table, "histTimeStamp_offset", histTimeStamp_offset) + call get_toml_val(table, "outputInflow", outputInflow) + call get_toml_val(table, "qgwl_runoff_option", qgwl_runoff_option) + call get_toml_val(table, "bypass_routing_option", bypass_routing_option) + call get_toml_val(table, "correct_area", correct_area) + call get_toml_val(table, "ice_runoff", ice_runoff) + call get_toml_val(table, "varname_area", meta_HRU(ixHRU%area)%varName) + call get_toml_val(table, "varname_HRUid", meta_HRU2SEG(ixHRU2SEG%HRUid)%varName) + call get_toml_val(table, "varname_HRUindex", meta_HRU2SEG(ixHRU2SEG%HRUindex)%varName) + call get_toml_val(table, "varname_hruSegId", meta_HRU2SEG(ixHRU2SEG%hruSegId)%varName) + call get_toml_val(table, "varname_hruSegIndex", meta_HRU2SEG(ixHRU2SEG%hruSegIndex)%varName) + call get_toml_val(table, "varname_length", meta_SEG(ixSEG%length)%varName) + call get_toml_val(table, "varname_slope", meta_SEG(ixSEG%slope)%varName) + call get_toml_val(table, "varname_width", meta_SEG(ixSEG%width)%varName) + call get_toml_val(table, "varname_depth", meta_SEG(ixSEG%depth)%varName) + call get_toml_val(table, "varname_sideSlope", meta_SEG(ixSEG%sideSlope)%varName) + call get_toml_val(table, "varname_man_n", meta_SEG(ixSEG%man_n)%varName) + call get_toml_val(table, "varname_floodplainSlope", meta_SEG(ixSEG%floodplainSlope)%varName) + call get_toml_val(table, "varname_hruArea", meta_SEG(ixSEG%hruArea)%varName) + call get_toml_val(table, "varname_weight", meta_SEG(ixSEG%weight)%varName) + call get_toml_val(table, "varname_timeDelayHist", meta_SEG(ixSEG%timeDelayHist)%varName) + call get_toml_val(table, "varname_upsArea", meta_SEG(ixSEG%upsArea)%varName) + call get_toml_val(table, "varname_hruContribIx", meta_NTOPO(ixNTOPO%hruContribIx)%varName) + call get_toml_val(table, "varname_hruContribId", meta_NTOPO(ixNTOPO%hruContribId)%varName) + call get_toml_val(table, "varname_segId", meta_NTOPO(ixNTOPO%segId)%varName) + call get_toml_val(table, "varname_segIndex", meta_NTOPO(ixNTOPO%segIndex)%varName) + call get_toml_val(table, "varname_downSegId", meta_NTOPO(ixNTOPO%downSegId)%varName) + call get_toml_val(table, "varname_downSegIndex", meta_NTOPO(ixNTOPO%downSegIndex)%varName) + call get_toml_val(table, "varname_upSegIds", meta_NTOPO(ixNTOPO%upSegIds)%varName) + call get_toml_val(table, "varname_upSegIndices", meta_NTOPO(ixNTOPO%upSegIndices)%varName) + call get_toml_val(table, "varname_rchOrder", meta_NTOPO(ixNTOPO%rchOrder)%varName) + call get_toml_val(table, "varname_lakeId", meta_NTOPO(ixNTOPO%lakeId)%varName) + call get_toml_val(table, "varname_lakeIndex", meta_NTOPO(ixNTOPO%lakeIndex)%varName) + call get_toml_val(table, "varname_isLakeInlet", meta_NTOPO(ixNTOPO%isLakeInlet)%varName) + call get_toml_val(table, "varname_islake", meta_NTOPO(ixNTOPO%islake)%varName) + call get_toml_val(table, "varname_lakeModelType", meta_NTOPO(ixNTOPO%lakeModelType)%varName) + call get_toml_val(table, "varname_LakeTargVol", meta_NTOPO(ixNTOPO%LakeTargVol)%varName) + call get_toml_val(table, "varname_userTake", meta_NTOPO(ixNTOPO%userTake)%varName) + call get_toml_val(table, "varname_goodBasin", meta_NTOPO(ixNTOPO%goodBasin)%varName) + call get_toml_val(table, "varname_pfafCode", meta_PFAF(ixPFAF%code)%varName) + call get_toml_val(table, "varname_D03_Coefficient", meta_SEG(ixSEG%D03_Coefficient)%varName) + call get_toml_val(table, "varname_H06_denominator", meta_SEG(ixSEG%H06_denominator)%varName) + + ! Output history flags + call get_toml_val(table, "basRunoff", meta_hflx(ixHFLX%basRunoff)%varFile) + call get_toml_val(table, "instRunoff", meta_rflx(ixRFLX%instRunoff)%varFile) + call get_toml_val(table, "dlayRunoff", meta_rflx(ixRFLX%dlayRunoff)%varFile) + call get_toml_val(table, "sumUpstreamRunoff", meta_rflx(ixRFLX%sumUpstreamRunoff)%varFile) + call get_toml_val(table, "KWTroutedRunoff", meta_rflx(ixRFLX%KWTroutedRunoff)%varFile) + call get_toml_val(table, "IRFroutedRunoff", meta_rflx(ixRFLX%IRFroutedRunoff)%varFile) + call get_toml_val(table, "KWroutedRunoff", meta_rflx(ixRFLX%KWroutedRunoff)%varFile) + call get_toml_val(table, "DWroutedRunoff", meta_rflx(ixRFLX%DWroutedRunoff)%varFile) + call get_toml_val(table, "MCroutedRunoff", meta_rflx(ixRFLX%MCroutedRunoff)%varFile) + call get_toml_val(table, "IRFvolume", meta_rflx(ixRFLX%IRFvolume)%varFile) + call get_toml_val(table, "KWTvolume", meta_rflx(ixRFLX%KWTvolume)%varFile) + call get_toml_val(table, "KWvolume", meta_rflx(ixRFLX%KWvolume)%varFile) + call get_toml_val(table, "MCvolume", meta_rflx(ixRFLX%MCvolume)%varFile) + call get_toml_val(table, "DWvolume", meta_rflx(ixRFLX%DWvolume)%varFile) + call get_toml_val(table, "KWfloodVolume", meta_rflx(ixRFLX%KWheight)%varFile) + call get_toml_val(table, "KWheight", meta_rflx(ixRFLX%KWheight)%varFile) + call get_toml_val(table, "MCfloodVolume", meta_rflx(ixRFLX%MCheight)%varFile) + call get_toml_val(table, "MCheight", meta_rflx(ixRFLX%MCheight)%varFile) + call get_toml_val(table, "DWfloodVolume", meta_rflx(ixRFLX%DWheight)%varFile) + call get_toml_val(table, "DWheight", meta_rflx(ixRFLX%DWheight)%varFile) + call get_toml_val(table, "localSolute", meta_rflx(ixRFLX%localSolute)%varFile) + call get_toml_val(table, "soluteFlux", meta_rflx(ixRFLX%DWsoluteFlux)%varFile) + call get_toml_val(table, "soluteMass", meta_rflx(ixRFLX%DWsoluteMass)%varFile) + + call validate_and_finalize_control(err, message) + + END SUBROUTINE read_control_toml + + ! ======================================================================================================= + ! public subroutine: read legacy format control file + ! ======================================================================================================= + SUBROUTINE read_control_legacy(ctl_fname, err, message) + ! global vars + USE globalData + USE var_lookup + USE ascii_utils, ONLY: file_open ! open file (performs a few checks as well) + USE ascii_utils, ONLY: get_vlines ! get a list of character strings from non-comment lines + + implicit none + ! argument variables + character(*), intent(in) :: ctl_fname ! name of the control file + integer(i4b),intent(out) :: err ! error code + character(*),intent(out) :: message ! error message + ! Local variables + character(len=strLen),allocatable :: cLines(:) ! vector of character strings + character(len=strLen) :: cName,cData ! name and data from cLines(iLine) + integer(i4b) :: ibeg_name ! start index of variable name in string cLines(iLine) + integer(i4b) :: iend_name ! end index of variable name in string cLines(iLine) + integer(i4b) :: iend_data ! end index of data in string cLines(iLine) + integer(i4b) :: iLine ! index of line in cLines + integer(i4b) :: iunit ! file unit + integer(i4b) :: io_error ! error in I/O + character(len=strLen) :: cmessage ! error message from subroutine + + err=0; message='read_control_legacy/' + + if (masterproc) then + write(iulog,'(2a)') new_line('a'), '---- read control file --- ' + end if + + call file_open(trim(ctl_fname),iunit,err,cmessage) + if(err/=0)then; message=trim(message)//trim(cmessage);return;endif + + call get_vlines(iunit,cLines,err,cmessage) + if(err/=0)then; message=trim(message)//trim(cmessage);return;endif + + close(iunit) ! loop through the non-comment lines in the input file, and extract the name and the information do iLine=1,size(cLines) @@ -384,7 +614,57 @@ SUBROUTINE read_control(ctl_fname, err, message) end do ! looping through lines in the control file - ! ---------- Perform minor processing and checking control variables ---------------------------------------- + call validate_and_finalize_control(err, message) + + END SUBROUTINE read_control_legacy + + ! ======================================================================================================= + ! private subroutine: common validation and post-processing for control variables + ! ======================================================================================================= + SUBROUTINE validate_and_finalize_control(err, message) + ! global vars + USE globalData, ONLY: time_conv,length_conv ! conversion factors + USE globalData, ONLY: time_conv_solute ! time conversion factor for solute mass + USE globalData, ONLY: mass_conv_solute ! mass conversion factors + USE globalData, ONLY: masterproc ! procs id and number of procs + ! metadata structures + USE globalData, ONLY: meta_HRU ! HRU properties + USE globalData, ONLY: meta_HRU2SEG ! HRU-to-segment mapping + USE globalData, ONLY: meta_SEG ! stream segment properties + USE globalData, ONLY: meta_NTOPO ! network topology + USE globalData, ONLY: meta_PFAF ! pfafstetter code + USE globalData, ONLY: meta_rflx ! river flux variables + USE globalData, ONLY: meta_hflx ! river flux variables + USE globalData, ONLY: isColdStart ! initial river state - cold start (T) or from restart file (F) + USE globalData, ONLY: nRoutes ! number of active routing methods + USE globalData, ONLY: routeMethods ! active routing method index and id + USE globalData, ONLY: onRoute ! logical to indicate actiive routing method(s) + USE globalData, ONLY: idxSUM,idxIRF,idxKWT, & + idxKW,idxMC,idxDW + USE globalData, ONLY: runMode ! mizuRoute run mode: standalone, cesm-coupling + ! index of named variables in each structure + USE var_lookup, ONLY: ixHRU + USE var_lookup, ONLY: ixHRU2SEG + USE var_lookup, ONLY: ixSEG + USE var_lookup, ONLY: ixNTOPO + USE var_lookup, ONLY: ixPFAF + USE var_lookup, ONLY: ixRFLX + USE var_lookup, ONLY: ixHFLX + USE nr_utils, ONLY: char2int ! convert integer number to a array containing individual digits + USE ascii_utils, ONLY: lower ! convert string to lower case + + implicit none + integer(i4b), intent(out) :: err ! error code + character(*), intent(out) :: message ! error message + character(len=strLen) :: cLength,cTime ! length and time units + character(len=strLen) :: cMass ! mass units needed only when tracer is on + integer(i4b) :: ipos ! index of character string + logical(lgt) :: isGeneric, onlyOneRouting + integer(i4b) :: iRoute ! loop index + + err=0; message='validate_and_finalize_control/' + + ! ---------- Perform minor processing and checking control variables ---------------------------------------- ! ---------- directory option --------------------------------------------------------------------- if (trim(restart_dir)==charMissing) then @@ -724,6 +1004,131 @@ SUBROUTINE read_control(ctl_fname, err, message) meta_rflx(ixRFLX%DWsoluteMass)%varFile = .false. endif - END SUBROUTINE read_control + END SUBROUTINE validate_and_finalize_control + + ! ======================================================================================================= + ! private function: validate control variable key name + ! ======================================================================================================= + PURE LOGICAL FUNCTION is_valid_control_key(key) + character(*), intent(in) :: key + select case(trim(key)) + case('ancil_dir', 'input_dir', 'output_dir', 'restart_dir', & + 'case_name', 'sim_start', 'sim_end', 'continue_run', 'route_opt', & + 'doesBasinRoute', 'dt_qsim', 'floodplain', 'hw_drain_point', 'tracer', & + 'is_lake_sim', 'lakeRegulate', 'LakeInputOption', 'is_flux_wm', 'is_vol_wm', & + 'is_vol_wm_jumpstart', 'scale_factor_runoff', 'offset_value_runoff', & + 'scale_factor_Ep', 'offset_value_Ep', 'is_Ep_upward_negative', & + 'scale_factor_prec', 'offset_value_prec', 'min_length_route', & + 'fname_ntopOld', 'ntopAugmentMode', 'fname_ntopNew', 'dname_nhru', 'dname_sseg', & + 'fname_qsim', 'vname_qsim', 'vname_evapo', 'vname_precip', 'vname_solute', & + 'vname_time', 'vname_hruid', 'dname_time', 'dname_hruid', 'dname_xlon', 'dname_ylat', & + 'units_qsim', 'units_cc', 'dt_ro', 'input_fillvalue', 'ro_calendar', & + 'ro_time_units', 'ro_time_stamp', 'runoffMin', 'fname_wm', 'vname_flux_wm', & + 'vname_vol_wm', 'vname_time_wm', 'vname_segid_wm', 'dname_time_wm', & + 'dname_segid_wm', 'dt_wm', 'is_remap', 'fname_remap', 'vname_hruid_in_remap', & + 'vname_weight', 'vname_qhruid', 'vname_num_qhru', 'vname_i_index', 'vname_j_index', & + 'dname_hru_remap', 'dname_data_remap', 'restart_write', 'restart_date', & + 'restart_month', 'restart_day', 'restart_hour', 'fname_state_in', 'param_nml', & + 'qmodOption', 'qBlendPeriod', 'QerrTrend', 'hydGeometryOption', 'topoNetworkOption', & + 'computeReachList', 'gageMetaFile', 'outputAtGage', 'fname_gageObs', 'vname_gageFlow', & + 'vname_gageSite', 'vname_gageTime', 'dname_gageSite', 'dname_gageTime', 'strlen_gageSite', & + 'pio_netcdf_format', 'pio_netcdf_type', 'debug', 'seg_outlet', 'desireId', & + 'checkMassBalance', 'maxPfafLen', 'pfafMissing', 'time_units', 'newFileFrequency', & + 'outputFrequency', 'outputNameOption', 'histTimeStamp_offset', 'outputInflow', & + 'qgwl_runoff_option', 'bypass_routing_option', 'correct_area', 'ice_runoff', & + 'varname_area', 'varname_HRUid', 'varname_HRUindex', 'varname_hruSegId', & + 'varname_hruSegIndex', 'varname_length', 'varname_slope', 'varname_width', & + 'varname_depth', 'varname_sideSlope', 'varname_man_n', 'varname_floodplainSlope', & + 'varname_hruArea', 'varname_weight', 'varname_timeDelayHist', 'varname_upsArea', & + 'varname_basUnderLake', 'varname_rchUnderLake', 'varname_minFlow', 'varname_D03_MaxStorage', & + 'varname_D03_Coefficient', 'varname_D03_Power', 'varname_D03_S0', 'varname_HYP_E_emr', & + 'varname_HYP_E_lim', 'varname_HYP_E_min', 'varname_HYP_E_zero', 'varname_HYP_Qrate_emr', & + 'varname_HYP_Erate_emr', 'varname_HYP_Qrate_prim', 'varname_HYP_Qrate_amp', & + 'varname_HYP_Qrate_phs', 'varname_HYP_prim_F', 'varname_HYP_A_avg', 'varname_HYP_Qsim_mode', & + 'varname_H06_Smax', 'varname_H06_alpha', 'varname_H06_envfact', 'varname_H06_S_ini', & + 'varname_H06_c1', 'varname_H06_c2', 'varname_H06_exponent', 'varname_H06_denominator', & + 'varname_H06_c_compare', 'varname_H06_frac_Sdead', 'varname_H06_E_rel_ini', & + 'varname_H06_I_Jan', 'varname_H06_I_Feb', 'varname_H06_I_Mar', 'varname_H06_I_Apr', & + 'varname_H06_I_May', 'varname_H06_I_Jun', 'varname_H06_I_Jul', 'varname_H06_I_Aug', & + 'varname_H06_I_Sep', 'varname_H06_I_Oct', 'varname_H06_I_Nov', 'varname_H06_I_Dec', & + 'varname_H06_D_Jan', 'varname_H06_D_Feb', 'varname_H06_D_Mar', 'varname_H06_D_Apr', & + 'varname_H06_D_May', 'varname_H06_D_Jun', 'varname_H06_D_Jul', 'varname_H06_D_Aug', & + 'varname_H06_D_Sep', 'varname_H06_D_Oct', 'varname_H06_D_Nov', 'varname_H06_D_Dec', & + 'varname_H06_purpose', 'varname_H06_I_mem_F', 'varname_H06_D_mem_F', 'varname_H06_I_mem_L', & + 'varname_H06_D_mem_L', 'varname_hruContribIx', 'varname_hruContribId', 'varname_segId', & + 'varname_segIndex', 'varname_downSegId', 'varname_downSegIndex', 'varname_upSegIds', & + 'varname_upSegIndices', 'varname_rchOrder', 'varname_lakeId', 'varname_lakeIndex', & + 'varname_isLakeInlet', 'varname_islake', 'varname_lakeModelType', 'varname_LakeTargVol', & + 'varname_userTake', 'varname_goodBasin', 'varname_pfafCode', 'basRunoff', 'instRunoff', & + 'dlayRunoff', 'sumUpstreamRunoff', 'KWTroutedRunoff', 'IRFroutedRunoff', 'KWroutedRunoff', & + 'DWroutedRunoff', 'MCroutedRunoff', 'IRFvolume', 'KWTvolume', 'KWvolume', 'MCvolume', & + 'DWvolume', 'KWfloodVolume', 'KWheight', 'MCfloodVolume', 'MCheight', 'DWfloodVolume', & + 'DWheight', 'localSolute', 'soluteFlux', 'soluteMass', 'KWTinflow', 'IRFinflow', & + 'KWinflow', 'MCinflow', 'DWinflow') + is_valid_control_key = .true. + case default + is_valid_control_key = .false. + end select + END FUNCTION is_valid_control_key + + SUBROUTINE get_toml_val_char(table, key, var) + type(toml_table), intent(inout) :: table + character(*), intent(in) :: key + character(*), intent(out) :: var + character(len=:), allocatable :: str_val + integer :: stat + call get_value(table, key, str_val, stat=stat) + if (stat == toml_stat%success .and. allocated(str_val)) then + var = str_val + endif + END SUBROUTINE get_toml_val_char + + SUBROUTINE get_toml_val_int(table, key, var) + type(toml_table), intent(inout) :: table + character(*), intent(in) :: key + integer(i4b), intent(inout) :: var + integer :: int_val + integer :: stat + call get_value(table, key, int_val, stat=stat) + if (stat == toml_stat%success) then + var = int(int_val, i4b) + endif + END SUBROUTINE get_toml_val_int + + SUBROUTINE get_toml_val_dp(table, key, var) + type(toml_table), intent(inout) :: table + character(*), intent(in) :: key + real(dp), intent(inout) :: var + real(dp) :: real_val + integer :: stat + call get_value(table, key, real_val, stat=stat) + if (stat == toml_stat%success) then + var = real_val + endif + END SUBROUTINE get_toml_val_dp + + SUBROUTINE get_toml_val_sp(table, key, var) + type(toml_table), intent(inout) :: table + character(*), intent(in) :: key + real(sp), intent(inout) :: var + real(sp) :: real_val + integer :: stat + call get_value(table, key, real_val, stat=stat) + if (stat == toml_stat%success) then + var = real_val + endif + END SUBROUTINE get_toml_val_sp + + SUBROUTINE get_toml_val_bool(table, key, var) + type(toml_table), intent(inout) :: table + character(*), intent(in) :: key + logical(lgt), intent(inout) :: var + logical :: bool_val + integer :: stat + call get_value(table, key, bool_val, stat=stat) + if (stat == toml_stat%success) then + var = bool_val + endif + END SUBROUTINE get_toml_val_bool END MODULE read_control_module diff --git a/route/build/test/test_standalone_derecho.sh b/route/build/test/test_standalone_derecho.sh new file mode 100755 index 000000000..5ea0fdec5 --- /dev/null +++ b/route/build/test/test_standalone_derecho.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# Standalone build and run test script for mizuRoute on NCAR Derecho (Intel compiler) +# Addresses ESCOMP/mizuRoute issue #647 + +set -e + +echo "=== mizuRoute Standalone Build & Run Tester (Derecho/Intel) ===" + +# 1. Environment setup +echo "Setting up module environment on Derecho..." +module purge || true +module load cmake intel cray-mpich netcdf-mpi ncarcompilers + +# 2. Check externals / submodules for mizuRoute (handling both standalone and CTSM component layouts) +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +BUILD_DIR="$( cd "${SCRIPT_DIR}/.." && pwd )" +MIZUROUTE_ROOT="$( cd "${BUILD_DIR}/../.." && pwd )" + +if [ ! -d "${MIZUROUTE_ROOT}/libraries/parallelio" ]; then + if [ -d "${MIZUROUTE_ROOT}/../../libraries/parallelio" ]; then + echo "Linking parallelio from parent CTSM checkout..." + mkdir -p "${MIZUROUTE_ROOT}/libraries" + ln -snf "${MIZUROUTE_ROOT}/../../libraries/parallelio" "${MIZUROUTE_ROOT}/libraries/parallelio" + fi +fi + +if [ ! -d "${MIZUROUTE_ROOT}/externals/toml-f" ]; then + if [ -d "${MIZUROUTE_ROOT}/../../externals/toml-f" ]; then + echo "Linking toml-f from parent CTSM checkout..." + mkdir -p "${MIZUROUTE_ROOT}/externals" + ln -snf "${MIZUROUTE_ROOT}/../../externals/toml-f" "${MIZUROUTE_ROOT}/externals/toml-f" + fi +fi + +# 3. Build standalone binary +echo "Building standalone mizuRoute binary in ${BUILD_DIR}..." +cd "${BUILD_DIR}" +export BLDDIR="${BUILD_DIR}/../" + +rm -rf "${MIZUROUTE_ROOT}/externals/toml-f/_build" "${MIZUROUTE_ROOT}/externals/toml-f/_install" + +gmake clean FC=intel FC_EXE=mpif90 F_MASTER="${BLDDIR}" || true +gmake FC=intel FC_EXE=mpif90 F_MASTER="${BLDDIR}" NCDF_PATH="${NETCDF}" MODE=fast EXE=route_runoff + +# Verify standalone executable binary was created in route/bin/ by gmake install +EXE_BIN="${MIZUROUTE_ROOT}/route/bin/route_runoff" +if [ ! -f "${EXE_BIN}" ]; then + echo "ERROR: Standalone executable 'route_runoff' was not created at ${EXE_BIN}." + exit 1 +fi + +echo "SUCCESS: Standalone executable 'route_runoff' built successfully at ${EXE_BIN}." +echo "=== Standalone Build Test Passed ===" diff --git a/route/settings/SAMPLE-coupled.control b/route/settings/SAMPLE-coupled.control deleted file mode 100644 index 267158f4e..000000000 --- a/route/settings/SAMPLE-coupled.control +++ /dev/null @@ -1,94 +0,0 @@ -! **************************************************************************************************************************** -! **************************************************************************************************************************** -! ***** DEFINITION OF MODEL CONTROL INFORMATION ****************************************************************************** -! **************************************************************************************************************************** -! **************************************************************************************************************************** -! Note: lines starting with "!" are treated as comment lines -- there is no limit on the number of comment lines. -! lines starting with are read till "!" -! Do not inclue empty line without ! -! -! Please see route/build/src/read_control.f90 for complete options -! -! Any control variables not in this example use default values defined in public_var.f90. -! -! **************************************************************************************************************************** -! RUN CONTROL -! -------------------------------------------- - CASE_NAME ! name of simulation - 5 ! option for routing schemes (only one-option allowed) 1->IRF, 2->KWT, 3-> KW, 4->MC, 5->DW - 1 ! basin routing options 0-> no, 1->gamma-diestribution UH, otherwise error - T ! switch for water abstraction/injection - 1 ! how lateral flow is put into a headwater reach 1-> top of headwater, 2-> bottom of headwater - F ! logical: floodwater is computed, otherwise, channel is unlimited bank depth - STATE_IN_NC ! input restart netCDF name. remove for run without any particular initial channel states - monthly ! frequency for new output files (daily, monthly, yearly, single) - monthly ! time frequency used for temporal aggregation of output variables - numeric or daily, monthyly, or yearly - 3600 ! simulation time interval [sec] - F ! logical; T-> append output in existing history files. F-> write output in new history file - F ! debug verbosity level; T -> extra log output. F-> normal log output -! **************************************************************************************************************************** -! DEFINE DIRECTORIES -! -------------------------- - ANCIL_DIR ! directory containing ancillary data (river network data) - INPUT_DIR ! directory containing input data - OUTPUT_DIR ! directory containing output data -! **************************************************************************************************************************** -! DEFINE FINE NAME AND DIMENSIONS -! --------------------------------------- - NTOPO_NC ! netCDF name for River Network - DIMNAME_SEG ! dimension name of the stream segments - DIMNAME_HRU ! dimension name of the HRUs - UPDATED_NTOPO_NC ! netCDF name for augmented River Network - mm/s ! units of runoff e.g., mm/s - Basin_Area ! name of variable holding hru area - Length ! name of variable holding segment length - Slope ! name of variable holding segment slope - hruid ! name of variable holding HRU id - hru_seg_id ! name of variable holding the stream segment below each HRU - seg_id ! name of variable holding the ID of each stream segment - Tosegment ! name of variable holding the ID of the next downstream segment - PFAF ! name of variable holding the pfafstetter code -! **************************************************************************************************************************** -! Define options for lake handling -! ---------------------------------------------------- - F ! Switch lake simulation on/off (T=on/F=off) - F ! Switch reservoir on/off (T=on/F=off). F-> all the lakes are natural (use Doll model) - S_max ! Maximum lake volume (for Doll 2003 parametrisation) - Coeff ! name of varibale holding the coefficnet of stage-discharge relatioship for lake - power ! name of varibale holding the coefficnet of stage-discharge relatioship for lake - S_0 ! Maximum lake volume (for Doll 2003 parametrisation) - lake ! name of variable holding the islake flage (1=lake, 0=reach) - lake_type ! name of varibale holding lake type (0=endo, 1=Doll, 2=Hanasaki, 3=HYPE) - 2 ! fluxes for lake simulation; 0->evaporation+precipitation (default), 1->runoff, 2->evaporation+precipitation+runoff -! **************************************************************************************************************************** -! Namelist file name -! --------------------------- - PARAMETER_NML ! Namelist name containing spatially constant parameter values -! **************************************************************************************************************************** -! output options -! --------------------------- -! NOTE: discharge and volume output options -! Routing options not chosen (see ) will be ignored. -! --------------------------- - generic ! "generic": routing method dependet output does not include routing schem name - T ! HRU average runoff depth at HRU [L/T] - F ! intantaneou runoff volume from Local HRUs at reach [L3/T] - F ! delayed runoff voluem (discharge) from local HRUsi at reach [L3/T] - F ! accumulated total upstream discharge at reach [L3/T] - T ! diffusive wave routed discharge at reach [L3/T] - T ! diffusive wave volume at the end of time step at reach [L3] -! **************************************************************************************************************************** -! cesm-coupler: negative flow (qgwl and excess irrigation demand) handling option -! --------------------------- -! NOTE: is options for handling negative runoff from land model (qbwl or excess irrigation demand). -! direct_in_place: separate negative HRU runoff from other runoff components and send it to the nearest ocean point through a coupler. -! direct_to_outlet: send negative HRU runoff directly to the basin outlet (ocean or endoheic lake) and putting it into the outlet reach. -! is options for handling qgwl runoff from CLM. -! all: apply all qgwl runoff to bypass_routing, negative: apply only negative runoff, threshold: apply runoff below threshold - direct_to_outlet ! options: direct_in_place, or direct_to_outlet - negative ! options: all, negative, or threshold - F ! T => perform area correction between model and coupler areas - T ! T => ice runoff exported to cpler separately from liquid, otherwise ice is combined with liquid -! **************************************************************************************************************************** -! **************************************************************************************************************************** -! **************************************************************************************************************************** diff --git a/route/settings/SAMPLE-coupled_toml b/route/settings/SAMPLE-coupled_toml new file mode 100644 index 000000000..e74fb9c04 --- /dev/null +++ b/route/settings/SAMPLE-coupled_toml @@ -0,0 +1,94 @@ +# **************************************************************************************************************************** +# **************************************************************************************************************************** +# ***** DEFINITION OF MODEL CONTROL INFORMATION ****************************************************************************** +# **************************************************************************************************************************** +# **************************************************************************************************************************** +# Note: lines starting with "!" are treated as comment lines -- there is no limit on the number of comment lines. +# lines starting with are read till "!" +# Do not inclue empty line without ! +# +# Please see route/build/src/read_control.f90 for complete options +# +# Any control variables not in this example use default values defined in public_var.f90. +# +# **************************************************************************************************************************** +# RUN CONTROL +# -------------------------------------------- +case_name = "CASE_NAME" # name of simulation +route_opt = 5 # option for routing schemes (only one-option allowed) 1->IRF, 2->KWT, 3-> KW, 4->MC, 5->DW +doesBasinRoute = 1 # basin routing options 0-> no, 1->gamma-diestribution UH, otherwise error +is_flux_wm = true # switch for water abstraction/injection +hw_drain_point = 1 # how lateral flow is put into a headwater reach 1-> top of headwater, 2-> bottom of headwater +floodplain = false # logical: floodwater is computed, otherwise, channel is unlimited bank depth +fname_state_in = "STATE_IN_NC" # input restart netCDF name. remove for run without any particular initial channel states +newFileFrequency = "monthly" # frequency for new output files (daily, monthly, yearly, single) +outputFrequency = "monthly" # time frequency used for temporal aggregation of output variables - numeric or daily, monthyly, or yearly +dt_qsim = 3600 # simulation time interval [sec] +continue_run = false # logical; T-> append output in existing history files. F-> write output in new history file +debug = false # debug verbosity level; T -> extra log output. F-> normal log output +# **************************************************************************************************************************** +# DEFINE DIRECTORIES +# -------------------------- +ancil_dir = "ANCIL_DIR" # directory containing ancillary data (river network data) +input_dir = "INPUT_DIR" # directory containing input data +output_dir = "OUTPUT_DIR" # directory containing output data +# **************************************************************************************************************************** +# DEFINE FINE NAME AND DIMENSIONS +# --------------------------------------- +fname_ntopOld = "NTOPO_NC" # netCDF name for River Network +dname_sseg = "DIMNAME_SEG" # dimension name of the stream segments +dname_nhru = "DIMNAME_HRU" # dimension name of the HRUs +fname_ntopNew = "UPDATED_NTOPO_NC" # netCDF name for augmented River Network +units_qsim = "mm/s" # units of runoff e.g., mm/s +varname_area = "Basin_Area" # name of variable holding hru area +varname_length = "Length" # name of variable holding segment length +varname_slope = "Slope" # name of variable holding segment slope +varname_HRUid = "hruid" # name of variable holding HRU id +varname_hruSegId = "hru_seg_id" # name of variable holding the stream segment below each HRU +varname_segId = "seg_id" # name of variable holding the ID of each stream segment +varname_downSegId = "Tosegment" # name of variable holding the ID of the next downstream segment +varname_pfafCode = "PFAF" # name of variable holding the pfafstetter code +# **************************************************************************************************************************** +# Define options for lake handling +# ---------------------------------------------------- +is_lake_sim = false # Switch lake simulation on/off (T=on/F=off) +lakeRegulate = false # Switch reservoir on/off (T=on/F=off). F-> all the lakes are natural (use Doll model) +varname_D03_MaxStorage = "S_max" # Maximum lake volume (for Doll 2003 parametrisation) +varname_D03_Coefficient = "Coeff" # name of varibale holding the coefficnet of stage-discharge relatioship for lake +varname_D03_Power = "power" # name of varibale holding the coefficnet of stage-discharge relatioship for lake +varname_D03_S0 = "S_0" # Maximum lake volume (for Doll 2003 parametrisation) +varname_islake = "lake" # name of variable holding the islake flage (1=lake, 0=reach) +varname_lakeModelType = "lake_type" # name of varibale holding lake type (0=endo, 1=Doll, 2=Hanasaki, 3=HYPE) +LakeInputOption = 2 # fluxes for lake simulation; 0->evaporation+precipitation (default), 1->runoff, 2->evaporation+precipitation+runoff +# **************************************************************************************************************************** +# Namelist file name +# --------------------------- +param_nml = "PARAMETER_NML" # Namelist name containing spatially constant parameter values +# **************************************************************************************************************************** +# output options +# --------------------------- +# NOTE: discharge and volume output options +# Routing options not chosen (see ) will be ignored. +# --------------------------- +outputNameOption = "generic" # "generic": routing method dependet output does not include routing schem name +basRunoff = true # HRU average runoff depth at HRU [L/T] +instRunoff = false # intantaneou runoff volume from Local HRUs at reach [L3/T] +dlayRunoff = false # delayed runoff voluem (discharge) from local HRUsi at reach [L3/T] +sumUpstreamRunoff = false # accumulated total upstream discharge at reach [L3/T] +DWroutedRunoff = true # diffusive wave routed discharge at reach [L3/T] +DWvolume = true # diffusive wave volume at the end of time step at reach [L3] +# **************************************************************************************************************************** +# cesm-coupler: negative flow (qgwl and excess irrigation demand) handling option +# --------------------------- +# NOTE: is options for handling negative runoff from land model (qbwl or excess irrigation demand). +# direct_in_place: separate negative HRU runoff from other runoff components and send it to the nearest ocean point through a coupler. +# direct_to_outlet: send negative HRU runoff directly to the basin outlet (ocean or endoheic lake) and putting it into the outlet reach. +# is options for handling qgwl runoff from CLM. +# all: apply all qgwl runoff to bypass_routing, negative: apply only negative runoff, threshold: apply runoff below threshold +bypass_routing_option = "direct_to_outlet" # options: direct_in_place, or direct_to_outlet +qgwl_runoff_option = "negative" # options: all, negative, or threshold +correct_area = false # T => perform area correction between model and coupler areas +ice_runoff = true # T => ice runoff exported to cpler separately from liquid, otherwise ice is combined with liquid +# **************************************************************************************************************************** +# **************************************************************************************************************************** +# **************************************************************************************************************************** diff --git a/route/settings/SAMPLE.control b/route/settings/SAMPLE.control deleted file mode 100644 index d01c4b371..000000000 --- a/route/settings/SAMPLE.control +++ /dev/null @@ -1,154 +0,0 @@ -! **************************************************************************************************************************** -! **************************************************************************************************************************** -! ***** DEFINITION OF MODEL CONTROL INFORMATION ****************************************************************************** -! **************************************************************************************************************************** -! **************************************************************************************************************************** -! Note: lines starting with "!" are treated as comment lines -- there is no limit on the number of comment lines. -! lines starting with are read till "!" -! Do not inclue empty line without ! -! -! Please see route/build/src/read_control.f90 for complete options -! -! Any control variables not in this example use default values defined in public_var.f90. -! -! **************************************************************************************************************************** -! RUN CONTROL -! -------------------------------------------- - CASE_NAME ! name of simulation - yyyy-mm-dd hh:mm:ss ! time of simulation start. hh:mm:ss can be omitted - yyyy-mm-dd hh:mm:ss ! time of simulation end. hh:mm:ss can be omitted -!-- routing control - 5 ! options for routing schemes (multiple options allowed - e.g, 12345-no spaces/commas between routing ids): 1->IRF 2->KWT 3->KW 4->MC 5->DW - 1 ! basin routing options (default: 1) : 0-> no, 1->gamma-distribution UH, otherwise error - 86400 ! simulation time interval [sec] e.g., 86400 sec for daily step - 2 ! how to add lateral runoff to headwater reaches (default 2) . 1->top of reach, 2->bottom of reach -!-- restart controls - last ! restart write options (default: never) : never, daily, monthly, yearly, last, specified - yyyy-mm-dd hh:mm:ss ! restart date. activated only if is "specified" - INPUT_RESTART_NC ! input restart netCDF name. remove or 'coldstart' for run without any particular restart file -!-- lake and water management (wm) mode - F ! switch on (T) or off (F) lake simulation - F ! switch on (T) or off (F) abstraction from or injection to seg/lakes - F ! switch on (T) or off (F) target volume (threshold lake volume where water release is trigerred) - F ! switch on (T) or off (F) for start for lakes with target volume -! **************************************************************************************************************************** -! DEFINE DIRECTORIES -! -------------------------- - ANCIL_DIR ! directory containing ancillary data (runoff mapping data, river network data) - INPUT_DIR ! directory containing input data (runoff data) - OUTPUT_DIR ! directory containing output data -! **************************************************************************************************************************** -! DEFINE FINE NAME AND DIMENSIONS -! --------------------------------------- - NTOPO_NC ! netCDF name for River Network - DIMNAME_SEG ! dimension name of the stream segments. default name: seg - DIMNAME_HRU ! dimension name of the HRUs. default name: hru -! **************************************************************************************************************************** -! DEFINE RUNOFF FILE -! ---------------------------------- - RUNOFF_NC ! name of text file listing forcing (runoff, precip, evapo) netcdf (chronologically ordered) - VARNAME_RUNOFF ! name of runoff variable - VARNAME_PRECIP ! name of precipitation variable (used for only lake mode on) - VARNAME_EVAPO ! name of evaporation variable (used for only lake mode on) - VARNAME_TIME ! name of time variable - VARNAME_RO_HRU ! name of forcing HRU id variable (if 1D vector is input) - DIMNAME_XLON ! name of x(j) dimension (if 2D grid is input) - DIMNAME_YLAT ! name of y(i) dimension (if 2D grid is input) - DIMNAME_TIME ! name of time dimension - DIMNAME_RO_HRU ! name of the HRU dimension (if 1D vector is input) - mm/s ! units of runoff e.g., mm/s - 86400 ! time interval of the forcing [sec] e.g., 86400 sec for daily step -! water-management option - WM_NC_IN ! name of text file containing water-management netcdf (chronologically ordered) - VARNAME_WM_FLUX ! name of varibale for abstraction/injection - VARNAME_WM_VOL ! name of varibale for target volume for managed lakes - VARNAME_WM_TIME ! name of time variable - VARNAME_WM_SEG ! name of the segment (or lake) ID varibale in netCDFs - DIMNAME_WM_TIME ! name of time dimension - DIMNAME_WM_SEG ! name of segment/lake ID dimension -! **************************************************************************************************************************** -! PART 6: DEFINE RUNOFF MAPPING FILE -! ---------------------------------- - T ! logical whether or not runnoff needs to be mapped to river network HRU - MAPPING_NC ! name of runoff mapping netCDF - VARNAME_MAP_RN_HRU ! name of variable for river network HRUs within each river network HRU - VARNAME_MAP_WEIGHT ! name of variable for areal weights of runoff HRUs within each river network HRU - VARNAME_MAP_RO_HRU ! name of variable for runoff HRU ID (if 1D runoff vector is input) - VARNAME_MAP_NUM_RO_HRU ! name of variable for a numbers of runoff HRUs within each river network HRU - DIMNAME_MAP_RN_HRU ! name of hru dimension (if 1D runoff vector is input) - DIMNAME_MAP_DATA ! name of data dimension - DIMNAME_I_INDEX ! name of ylat index dimension (if 2D runoff grid is input) - DIMNAME_J_INDEX ! name of xlon index dimension (if 2D runoff grid is input) -! **************************************************************************************************************************** -! Define options to include/skip calculations -! ---------------------------------------------------- - 1 ! option for hydraulic geometry calculations (0=read from file, 1=compute) - 1 ! option for network topology calculations (0=read from file, 1=compute) - 1 ! option to compute list of upstream reaches (0=do not compute, 1=compute) -! **************************************************************************************************************************** -! Namelist file name -! --------------------------- - PARAMETER_NML ! Namelist name containing spatially constant parameter values -! **************************************************************************************************************************** -! Dictionary to map variable names -! --------------------------- - Basin_Area ! name of variable holding hru area - Length ! name of variable holding segment length - Slope ! name of variable holding segment slope - hruid ! name of variable holding HRU id - seg_hru_id ! name of variable holding the stream segment below each HRU - seg_id ! name of variable holding the ID of each stream segment - tosegment ! name of variable holding the ID of the next downstream segment -! if lake mode is on - lake ! name of variable holding the islake flage (1=lake, 0=reach) - lake_type ! name of variable holding the lake model type - target_vol ! name of varibale to identify the target volume flag -! **************************************************************************************************************************** -! **************************************************************************************************************************** -! **************************************************************************************************************************** -! -! Some of useful input options -! See read_control.f90 for complete options -! -! **************************************************************************************************************************** -! Network augmentation or subsetting -! --------------------------- - SEG_ID ! seg_id of outlet streamflow segment to subset for upstream basin. - F ! option for river network augmentation mode - AUGMENTED_NTOPO_NC ! name of augmented or subsetted river network netCDF -! **************************************************************************************************************************** -! debugging -! --------------------------- - F ! print out detailed information throught the probram - -9999 ! turn off checks (-999) or speficy reach ID if necessary to print on screen -! **************************************************************************************************************************** -! output options -! --------------------------- -! NOTE: discharge and volume output options -! Routing options not chosen (see ) will be ignored. -! --------------------------- -!-- histrory file controls - monthly ! output netcdf frequency: single, daily, monthly, yearly - daily ! output frequency (integer for multiple of simulation time step or daily, monthly or yearly) - 0 ! time stamp offset [second] from a start of time step -! -- non routing option specific variables - T ! HRU average runoff depth at HRU [L/T] - T ! intantaneou runoff volume from Local HRUs at reach [L3/T] - T ! delayed runoff voluem (discharge) from local HRUsi at reach [L3/T] - T ! accumulated total upstream discharge at reach [L3/T] -!-- routing option specific variables - T ! kinematic wave tracking routed discharge at reach [L3/T] - T ! impulse response function routed discharge at reach [L3/T] - T ! kinematic wave routed discharge at reach [L3/T] - T ! muskingum-cunge routed discharge at reach [L3/T] - T ! diffusive wave routed discharge at reach [L3/T] - F ! volume at the end of time step at reach [L3] - kinematic wave tracking - F ! volume at the end of time step at reach [L3] - impulse response function - F ! volume at the end of time step at reach [L3] - kinematic wave - F ! volume at the end of time step at reach [L3] - muskingum-cunge - F ! volume at the end of time step at reach [L3] - diffusive wave - F ! inflow into reach [L3] - kinematic wave tracking - F ! inflow into reach [L3] - impulse response function - F ! inflow into reach [L3] - kinematic wave - F ! inflow into reach [L3] - muskingum-cunge - F ! inflow into reach [L3] - diffusive wave diff --git a/route/settings/SAMPLE_toml b/route/settings/SAMPLE_toml new file mode 100644 index 000000000..cb2bcfc8f --- /dev/null +++ b/route/settings/SAMPLE_toml @@ -0,0 +1,154 @@ +# **************************************************************************************************************************** +# **************************************************************************************************************************** +# ***** DEFINITION OF MODEL CONTROL INFORMATION ****************************************************************************** +# **************************************************************************************************************************** +# **************************************************************************************************************************** +# Note: lines starting with "!" are treated as comment lines -- there is no limit on the number of comment lines. +# lines starting with are read till "!" +# Do not inclue empty line without ! +# +# Please see route/build/src/read_control.f90 for complete options +# +# Any control variables not in this example use default values defined in public_var.f90. +# +# **************************************************************************************************************************** +# RUN CONTROL +# -------------------------------------------- +case_name = "CASE_NAME" # name of simulation +sim_start = "yyyy-mm-dd hh:mm:ss" # time of simulation start. hh:mm:ss can be omitted +sim_end = "yyyy-mm-dd hh:mm:ss" # time of simulation end. hh:mm:ss can be omitted +#-- routing control +route_opt = 5 # options for routing schemes (multiple options allowed - e.g, 12345-no spaces/commas between routing ids): 1->IRF 2->KWT 3->KW 4->MC 5->DW +doesBasinRoute = 1 # basin routing options (default: 1) : 0-> no, 1->gamma-distribution UH, otherwise error +dt_qsim = 86400 # simulation time interval [sec] e.g., 86400 sec for daily step +hw_drain_point = 2 # how to add lateral runoff to headwater reaches (default 2) . 1->top of reach, 2->bottom of reach +#-- restart controls +restart_write = "last" # restart write options (default: never) : never, daily, monthly, yearly, last, specified +restart_date = "yyyy-mm-dd hh:mm:ss" # restart date. activated only if is "specified" +fname_state_in = "INPUT_RESTART_NC" # input restart netCDF name. remove or 'coldstart' for run without any particular restart file +#-- lake and water management (wm) mode +is_lake_sim = false # switch on (T) or off (F) lake simulation +is_flux_wm = false # switch on (T) or off (F) abstraction from or injection to seg/lakes +is_vol_wm = false # switch on (T) or off (F) target volume (threshold lake volume where water release is trigerred) +is_vol_wm_jumpstart = false # switch on (T) or off (F) for start for lakes with target volume +# **************************************************************************************************************************** +# DEFINE DIRECTORIES +# -------------------------- +ancil_dir = "ANCIL_DIR" # directory containing ancillary data (runoff mapping data, river network data) +input_dir = "INPUT_DIR" # directory containing input data (runoff data) +output_dir = "OUTPUT_DIR" # directory containing output data +# **************************************************************************************************************************** +# DEFINE FINE NAME AND DIMENSIONS +# --------------------------------------- +fname_ntopOld = "NTOPO_NC" # netCDF name for River Network +dname_sseg = "DIMNAME_SEG" # dimension name of the stream segments. default name: seg +dname_nhru = "DIMNAME_HRU" # dimension name of the HRUs. default name: hru +# **************************************************************************************************************************** +# DEFINE RUNOFF FILE +# ---------------------------------- +fname_qsim = "RUNOFF_NC" # name of text file listing forcing (runoff, precip, evapo) netcdf (chronologically ordered) +vname_qsim = "VARNAME_RUNOFF" # name of runoff variable +vname_precip = "VARNAME_PRECIP" # name of precipitation variable (used for only lake mode on) +vname_evapo = "VARNAME_EVAPO" # name of evaporation variable (used for only lake mode on) +vname_time = "VARNAME_TIME" # name of time variable +vname_hruid = "VARNAME_RO_HRU" # name of forcing HRU id variable (if 1D vector is input) +dname_xlon = "DIMNAME_XLON" # name of x(j) dimension (if 2D grid is input) +dname_ylat = "DIMNAME_YLAT" # name of y(i) dimension (if 2D grid is input) +dname_time = "DIMNAME_TIME" # name of time dimension +dname_hruid = "DIMNAME_RO_HRU" # name of the HRU dimension (if 1D vector is input) +units_qsim = "mm/s" # units of runoff e.g., mm/s +dt_ro = 86400 # time interval of the forcing [sec] e.g., 86400 sec for daily step +# water-management option +fname_wm = "WM_NC_IN" # name of text file containing water-management netcdf (chronologically ordered) +vname_flux_wm = "VARNAME_WM_FLUX" # name of varibale for abstraction/injection +vname_vol_wm = "VARNAME_WM_VOL" # name of varibale for target volume for managed lakes +vname_time_wm = "VARNAME_WM_TIME" # name of time variable +vname_segid_wm = "VARNAME_WM_SEG" # name of the segment (or lake) ID varibale in netCDFs +dname_time_wm = "DIMNAME_WM_TIME" # name of time dimension +dname_segid_wm = "DIMNAME_WM_SEG" # name of segment/lake ID dimension +# **************************************************************************************************************************** +# PART 6: DEFINE RUNOFF MAPPING FILE +# ---------------------------------- +is_remap = true # logical whether or not runnoff needs to be mapped to river network HRU +fname_remap = "MAPPING_NC" # name of runoff mapping netCDF +vname_hruid_in_remap = "VARNAME_MAP_RN_HRU" # name of variable for river network HRUs within each river network HRU +vname_weight = "VARNAME_MAP_WEIGHT" # name of variable for areal weights of runoff HRUs within each river network HRU +vname_qhruid = "VARNAME_MAP_RO_HRU" # name of variable for runoff HRU ID (if 1D runoff vector is input) +vname_num_qhru = "VARNAME_MAP_NUM_RO_HRU" # name of variable for a numbers of runoff HRUs within each river network HRU +dname_hru_remap = "DIMNAME_MAP_RN_HRU" # name of hru dimension (if 1D runoff vector is input) +dname_data_remap = "DIMNAME_MAP_DATA" # name of data dimension +vname_i_index = "DIMNAME_I_INDEX" # name of ylat index dimension (if 2D runoff grid is input) +vname_j_index = "DIMNAME_J_INDEX" # name of xlon index dimension (if 2D runoff grid is input) +# **************************************************************************************************************************** +# Define options to include/skip calculations +# ---------------------------------------------------- +hydGeometryOption = 1 # option for hydraulic geometry calculations (0=read from file, 1=compute) +topoNetworkOption = 1 # option for network topology calculations (0=read from file, 1=compute) +computeReachList = 1 # option to compute list of upstream reaches (0=do not compute, 1=compute) +# **************************************************************************************************************************** +# Namelist file name +# --------------------------- +param_nml = "PARAMETER_NML" # Namelist name containing spatially constant parameter values +# **************************************************************************************************************************** +# Dictionary to map variable names +# --------------------------- +varname_area = "Basin_Area" # name of variable holding hru area +varname_length = "Length" # name of variable holding segment length +varname_slope = "Slope" # name of variable holding segment slope +varname_HRUid = "hruid" # name of variable holding HRU id +varname_hruSegId = "seg_hru_id" # name of variable holding the stream segment below each HRU +varname_segId = "seg_id" # name of variable holding the ID of each stream segment +varname_downSegId = "tosegment" # name of variable holding the ID of the next downstream segment +# if lake mode is on +varname_islake = "lake" # name of variable holding the islake flage (1=lake, 0=reach) +varname_lakeModelType = "lake_type" # name of variable holding the lake model type +varname_LakeTargVol = "target_vol" # name of varibale to identify the target volume flag +# **************************************************************************************************************************** +# **************************************************************************************************************************** +# **************************************************************************************************************************** +# +# Some of useful input options +# See read_control.f90 for complete options +# +# **************************************************************************************************************************** +# Network augmentation or subsetting +# --------------------------- +seg_outlet = "SEG_ID" # seg_id of outlet streamflow segment to subset for upstream basin. +ntopAugmentMode = false # option for river network augmentation mode +fname_ntopNew = "AUGMENTED_NTOPO_NC" # name of augmented or subsetted river network netCDF +# **************************************************************************************************************************** +# debugging +# --------------------------- +debug = false # print out detailed information throught the probram +desireId = -9999 # turn off checks (-999) or speficy reach ID if necessary to print on screen +# **************************************************************************************************************************** +# output options +# --------------------------- +# NOTE: discharge and volume output options +# Routing options not chosen (see ) will be ignored. +# --------------------------- +#-- histrory file controls +newFileFrequency = "monthly" # output netcdf frequency: single, daily, monthly, yearly +outputFrequency = "daily" # output frequency (integer for multiple of simulation time step or daily, monthly or yearly) +histTimeStamp_offset = 0 # time stamp offset [second] from a start of time step +# -- non routing option specific variables +basRunoff = true # HRU average runoff depth at HRU [L/T] +instRunoff = true # intantaneou runoff volume from Local HRUs at reach [L3/T] +dlayRunoff = true # delayed runoff voluem (discharge) from local HRUsi at reach [L3/T] +sumUpstreamRunoff = true # accumulated total upstream discharge at reach [L3/T] +#-- routing option specific variables +KWTroutedRunoff = true # kinematic wave tracking routed discharge at reach [L3/T] +IRFroutedRunoff = true # impulse response function routed discharge at reach [L3/T] +MCroutedRunoff = true # kinematic wave routed discharge at reach [L3/T] +KWroutedRunoff = true # muskingum-cunge routed discharge at reach [L3/T] +DWroutedRunoff = true # diffusive wave routed discharge at reach [L3/T] +KWTvolume = false # volume at the end of time step at reach [L3] - kinematic wave tracking +IRFvolume = false # volume at the end of time step at reach [L3] - impulse response function +KWvolume = false # volume at the end of time step at reach [L3] - kinematic wave +MCvolume = false # volume at the end of time step at reach [L3] - muskingum-cunge +DWvolume = false # volume at the end of time step at reach [L3] - diffusive wave +KWTinflow = false # inflow into reach [L3] - kinematic wave tracking +IRFinflow = false # inflow into reach [L3] - impulse response function +KWinflow = false # inflow into reach [L3] - kinematic wave +MCinflow = false # inflow into reach [L3] - muskingum-cunge +DWinflow = false # inflow into reach [L3] - diffusive wave diff --git a/route/settings/mizuRoute_control.py b/route/settings/mizuRoute_control.py index 5b83a6899..e98db58f6 100644 --- a/route/settings/mizuRoute_control.py +++ b/route/settings/mizuRoute_control.py @@ -6,7 +6,7 @@ Erik Kluzek """ -import sys, re +import sys, re, os, logging, collections sys.path.append( "../../cime/scripts/lib" ); sys.path.append( "../../../../cime/scripts/lib" ); @@ -14,6 +14,11 @@ from CIME.XML.standard_module_setup import * from CIME.utils import expect, convert_to_string, convert_to_type, run_cmd_no_fail +try: + import tomllib +except ImportError: + import tomli as tomllib + logger = logging.getLogger(__name__) class mizuRoute_control(object): @@ -22,53 +27,153 @@ class mizuRoute_control(object): # Class Data: fileRead = False # If file has been read or not - lineMatch = '^<(.+?)>\s+(\S+)\s+\!(.+)$' # Pattern to match for lines + lineMatch = '^<(.+?)>\s+(.*?)\s*\!(.*)$' # Pattern to match for legacy lines longestName = 0 # Longest name longestValue = 0 # Longest value def __init__(self): - self.ctldict = {} # Dictionary of control elments - self.keyList = [] # List of keys for control elements - self.lines = [] # Lines of the entire file read in + self.ctldict = collections.OrderedDict() # Ordered dictionary of control elements + self.comments = {} # Comments associated with keys + + @classmethod + def from_toml( cls, infile, allowEmpty=False ): + """ Factory constructor to read and parse a TOML format control file """ + inst = cls() + inst.readToml( infile, allowEmpty=allowEmpty ) + return inst + + @classmethod + def from_control( cls, infile, allowEmpty=False ): + """ Factory constructor to read and parse a legacy format control file """ + inst = cls() + inst.readControl( infile, allowEmpty=allowEmpty ) + return inst def read( self, infile, allowEmpty=False ): """ - Read and parse a mizuRoute control file + Read and parse a mizuRoute control file (auto-detect format) + """ + if ( infile.endswith(".toml") or infile.endswith("_toml") or os.path.basename(infile) == "user_nl_mizuroute_toml" ): + return self.readToml( infile, allowEmpty=allowEmpty ) + else: + return self.readControl( infile, allowEmpty=allowEmpty ) + + def readControl( self, infile, allowEmpty=False ): + """ + Read and parse a legacy mizuRoute control file """ - # Read the whole file and save each line as object data - logger.debug( "read in file: "+infile ) + logger.debug( "read in legacy control file: "+infile ) if ( not os.path.exists(infile) ): expect( False, "Input file to read does NOT exist: "+infile ) ctlfile = open( infile, "r" ) - self.lines = ctlfile.readlines() + lines = ctlfile.readlines() ctlfile.close() # Loop through each line in the file - for line in self.lines: + for line in lines: # Ignore comment lines - if ( not line.find( "!" ) == 0 ): + if ( not line.find( "!" ) == 0 and line.strip() ): match = re.search( self.lineMatch, line ) if ( not match ): expect( False, "Error in reading in line:"+line ) else: - name = match.group(1) - value = match.group(2) - self.set( name, value, allowNewName=True ) - + name = match.group(1).strip() + value = match.group(2).strip() + comment = match.group(3).strip() if len(match.groups()) >= 3 and match.group(3) else "" + self.set( name, value, allowNewName=True, comment=comment ) # If no data was read -- abort with an error - if ( len(self.keyList) == 0 and not allowEmpty ): + if ( len(self.ctldict) == 0 and not allowEmpty ): expect( False, "No data was read from the file: "+infile ) # Mark the file as read logger.debug( "File read" ) self.fileRead = True + VALID_CONTROL_KEYS = { + 'ancil_dir', 'input_dir', 'output_dir', 'restart_dir', 'case_name', + 'sim_start', 'sim_end', 'continue_run', 'route_opt', 'doesBasinRoute', + 'dt_qsim', 'floodplain', 'hw_drain_point', 'tracer', 'is_lake_sim', + 'lakeRegulate', 'LakeInputOption', 'is_flux_wm', 'is_vol_wm', + 'is_vol_wm_jumpstart', 'scale_factor_runoff', 'offset_value_runoff', + 'scale_factor_Ep', 'offset_value_Ep', 'is_Ep_upward_negative', + 'scale_factor_prec', 'offset_value_prec', 'min_length_route', + 'fname_ntopOld', 'ntopAugmentMode', 'fname_ntopNew', 'dname_nhru', 'dname_sseg', + 'fname_qsim', 'vname_qsim', 'vname_evapo', 'vname_precip', 'vname_solute', + 'vname_time', 'vname_hruid', 'dname_time', 'dname_hruid', 'dname_xlon', 'dname_ylat', + 'units_qsim', 'units_cc', 'dt_ro', 'input_fillvalue', 'ro_calendar', + 'ro_time_units', 'ro_time_stamp', 'runoffMin', 'fname_wm', 'vname_flux_wm', + 'vname_vol_wm', 'vname_time_wm', 'vname_segid_wm', 'dname_time_wm', + 'dname_segid_wm', 'dt_wm', 'is_remap', 'fname_remap', 'vname_hruid_in_remap', + 'vname_weight', 'vname_qhruid', 'vname_num_qhru', 'vname_i_index', 'vname_j_index', + 'dname_hru_remap', 'dname_data_remap', 'restart_write', 'restart_date', + 'restart_month', 'restart_day', 'restart_hour', 'fname_state_in', 'param_nml', + 'qmodOption', 'qBlendPeriod', 'QerrTrend', 'hydGeometryOption', 'topoNetworkOption', + 'computeReachList', 'gageMetaFile', 'outputAtGage', 'fname_gageObs', 'vname_gageFlow', + 'vname_gageSite', 'vname_gageTime', 'dname_gageSite', 'dname_gageTime', 'strlen_gageSite', + 'pio_netcdf_format', 'pio_netcdf_type', 'debug', 'seg_outlet', 'desireId', + 'checkMassBalance', 'maxPfafLen', 'pfafMissing', 'time_units', 'newFileFrequency', + 'outputFrequency', 'outputNameOption', 'histTimeStamp_offset', 'outputInflow', + 'qgwl_runoff_option', 'bypass_routing_option', 'correct_area', 'ice_runoff', + 'varname_area', 'varname_HRUid', 'varname_HRUindex', 'varname_hruSegId', + 'varname_hruSegIndex', 'varname_length', 'varname_slope', 'varname_width', + 'varname_depth', 'varname_sideSlope', 'varname_man_n', 'varname_floodplainSlope', + 'varname_hruArea', 'varname_weight', 'varname_timeDelayHist', 'varname_upsArea', + 'varname_basUnderLake', 'varname_rchUnderLake', 'varname_minFlow', 'varname_D03_MaxStorage', + 'varname_D03_Coefficient', 'varname_D03_Power', 'varname_D03_S0', 'varname_HYP_E_emr', + 'varname_HYP_E_lim', 'varname_HYP_E_min', 'varname_HYP_E_zero', 'varname_HYP_Qrate_emr', + 'varname_HYP_Erate_emr', 'varname_HYP_Qrate_prim', 'varname_HYP_Qrate_amp', + 'varname_HYP_Qrate_phs', 'varname_HYP_prim_F', 'varname_HYP_A_avg', 'varname_HYP_Qsim_mode', + 'varname_H06_Smax', 'varname_H06_alpha', 'varname_H06_envfact', 'varname_H06_S_ini', + 'varname_H06_c1', 'varname_H06_c2', 'varname_H06_exponent', 'varname_H06_denominator', + 'varname_H06_c_compare', 'varname_H06_frac_Sdead', 'varname_H06_E_rel_ini', + 'varname_H06_I_Jan', 'varname_H06_I_Feb', 'varname_H06_I_Mar', 'varname_H06_I_Apr', + 'varname_H06_I_May', 'varname_H06_I_Jun', 'varname_H06_I_Jul', 'varname_H06_I_Aug', + 'varname_H06_I_Sep', 'varname_H06_I_Oct', 'varname_H06_I_Nov', 'varname_H06_I_Dec', + 'varname_H06_D_Jan', 'varname_H06_D_Feb', 'varname_H06_D_Mar', 'varname_H06_D_Apr', + 'varname_H06_D_May', 'varname_H06_D_Jun', 'varname_H06_D_Jul', 'varname_H06_D_Aug', + 'varname_H06_D_Sep', 'varname_H06_D_Oct', 'varname_H06_D_Nov', 'varname_H06_D_Dec', + 'varname_H06_purpose', 'varname_H06_I_mem_F', 'varname_H06_D_mem_F', 'varname_H06_I_mem_L', + 'varname_H06_D_mem_L', 'varname_hruContribIx', 'varname_hruContribId', 'varname_segId', + 'varname_segIndex', 'varname_downSegId', 'varname_downSegIndex', 'varname_upSegIds', + 'varname_upSegIndices', 'varname_rchOrder', 'varname_lakeId', 'varname_lakeIndex', + 'varname_isLakeInlet', 'varname_islake', 'varname_lakeModelType', 'varname_LakeTargVol', + 'varname_userTake', 'varname_goodBasin', 'varname_pfafCode', 'basRunoff', 'instRunoff', + 'dlayRunoff', 'sumUpstreamRunoff', 'KWTroutedRunoff', 'IRFroutedRunoff', 'KWroutedRunoff', + 'DWroutedRunoff', 'MCroutedRunoff', 'IRFvolume', 'KWTvolume', 'KWvolume', 'MCvolume', + 'DWvolume', 'KWfloodVolume', 'KWheight', 'MCfloodVolume', 'MCheight', 'DWfloodVolume', + 'DWheight', 'localSolute', 'soluteFlux', 'soluteMass', 'KWTinflow', 'IRFinflow', + 'KWinflow', 'MCinflow', 'DWinflow' + } + + def readToml( self, infile, allowEmpty=False ): + """ + Read and parse a mizuRoute TOML control file + """ + logger.debug( "read in TOML file: "+infile ) + if ( not os.path.exists(infile) ): + expect( False, "Input file to read does NOT exist: "+infile ) - def write( self, outfile ): + with open( infile, "r" ) as ctlfile: + content = ctlfile.read() + + parsed_toml = tomllib.loads(content) + for key, val in parsed_toml.items(): + if key not in self.VALID_CONTROL_KEYS: + expect( False, f"Unexpected variable in TOML control file: {key}" ) + val_str = str(val) if not isinstance(val, bool) else ('true' if val else 'false') + self.set( key, val_str, allowNewName=True ) + + if ( len(self.ctldict) == 0 and not allowEmpty ): + expect( False, "No data was read from the file: "+infile ) + + logger.debug( "File read" ) + self.fileRead = True + + def write_legacy( self, outfile ): """ - Write out a mizuRoute control file + Write out a mizuRoute control file in legacy format """ logger.debug( "Write out file: "+outfile ) @@ -76,21 +181,37 @@ def write( self, outfile ): os.remove( outfile ) ctlfile = open( outfile, "w" ) vallen = str(self.longestValue + 1) - # Loop through each line in the file - for line in self.lines: - # Write comment lines as is - if ( line.find( "!" ) == 0 ): - ctlfile.write( line ) + for name, value in self.ctldict.items(): + comment = self.comments.get(name, "") + namelen = str(self.longestName - len(name) + 1) + format = "<%s>%"+namelen+"s %-"+vallen+"s ! %s\n" + ctlfile.write( format % (name, " ", value, comment) ) + + ctlfile.close() + + def write( self, outfile ): + """ + Write out a mizuRoute control file in TOML format + """ + logger.debug( "Write out file: "+outfile ) + + if ( os.path.exists(outfile) ): + os.remove( outfile ) + ctlfile = open( outfile, "w" ) + for name, value in self.ctldict.items(): + val_str = str(value) + if val_str.isdigit() or (val_str.startswith("-") and val_str[1:].isdigit()): + formatted_val = val_str + elif val_str.replace('.','',1).isdigit() or (val_str.startswith("-") and val_str[1:].replace('.','',1).isdigit()): + formatted_val = val_str + elif val_str.lower() in ['t', 'f', 'true', 'false', '.true.', '.false.']: + formatted_val = 'true' if val_str.lower() in ['t', 'true', '.true.'] else 'false' else: - match = re.search( self.lineMatch, line ) - if ( not match ): - expect( False, "Error in for output line:"+line ) - name = match.group(1) - value = self.get( name ) - comment = match.group(3) - namelen = str(self.longestName - len(name) + 1) - format = "<%s>%"+namelen+"s %-"+vallen+"s ! %s\n" - ctlfile.write( format % (name, " ", value, comment) ) + formatted_val = f'"{val_str}"' + + comment = self.comments.get(name, "") + comment_str = f" # {comment}" if comment else "" + ctlfile.write( f"{name} = {formatted_val}{comment_str}\n" ) ctlfile.close() @@ -98,25 +219,26 @@ def get( self, name ): """ Return an element from the control file """ - if ( self.__is_valid_name( name ) ): - return( self.ctldict[name] ) - else: - return( "UNSET" ) + return self.ctldict.get(name, "UNSET") - def set( self, name, value, allowNewName=False ): + def set( self, name, value, allowNewName=False, comment="" ): """ Set an element in the control file """ - self.ctldict[name] = value - # Check for longest value and name if ( len(name) > self.longestName ): self.longestName = len(name) - if ( len(value) > self.longestValue ): self.longestValue = len(value) + if ( len(str(value)) > self.longestValue ): self.longestValue = len(str(value)) - if ( not self.__is_valid_name( name ) ): + if ( not self._is_valid_name( name ) ): if ( allowNewName ): - self.keyList.append(name) + self.ctldict[name] = str(value) + if comment: + self.comments[name] = comment else: expect( False, "set method is operating on a name that doesn't exist:"+name ) + else: + self.ctldict[name] = str(value) + if comment: + self.comments[name] = comment def get_elmList( self ): """ @@ -125,21 +247,16 @@ def get_elmList( self ): if ( not self.is_read() ): expect( False, "mizuRoute control file was NOT read in yet, need to do that before returning list of elements" ) - elmList = list(self.keyList) - return( elmList ) + return list(self.ctldict.keys()) - def __is_valid_name( self, name ): + def _is_valid_name( self, name ): """ Check if the name is valid """ if ( self.is_read() ): - try: - idx = self.keyList.index(name) - return( True ) - except ValueError: - return( False ) + return name in self.ctldict else: - return( False ) + return False def is_read( self ): """ @@ -160,13 +277,13 @@ def setUp( self ): def test_is_read( self ): self.assertFalse( self.ctl.is_read() ) - self.ctl.read( "SAMPLE.control" ) + self.ctl.read( "SAMPLE_toml" ) self.assertTrue( self.ctl.is_read() ) def test_get_list_of_elments( self ): - self.ctl.read( "SAMPLE.control" ) + self.ctl.read( "SAMPLE_toml" ) elist = self.ctl.get_elmList( ) - expected = ['ancil_dir', 'input_dir', 'output_dir', 'sim_start', 'sim_end', 'fname_ntopOld', + expected_subset = ['ancil_dir', 'input_dir', 'output_dir', 'sim_start', 'sim_end', 'fname_ntopOld', 'dname_sseg', 'dname_nhru', 'fname_ntopNew', 'seg_outlet', 'fname_qsim', 'vname_qsim', 'vname_time', 'vname_hruid', 'dname_xlon', @@ -179,15 +296,51 @@ def test_get_list_of_elments( self ): 'computeReachList', 'param_nml', 'varname_area', 'varname_length', 'varname_slope', 'varname_HRUid', 'varname_hruSegId', 'varname_segId', 'varname_downSegId'] - self.assertEqual( expected, elist ) + for expected_item in expected_subset: + self.assertTrue(expected_item in elist, f"{expected_item} not in parsed list") + + def test_all_sample_fields_present( self ): + ctl = mizuRoute_control.from_toml( "SAMPLE_toml" ) + self.assertTrue( ctl.is_read() ) + expected_set_keys = { + 'DWinflow', 'DWroutedRunoff', 'DWvolume', 'IRFinflow', 'IRFroutedRunoff', 'IRFvolume', + 'KWTinflow', 'KWTroutedRunoff', 'KWTvolume', 'KWinflow', 'KWroutedRunoff', 'KWvolume', + 'MCinflow', 'MCroutedRunoff', 'MCvolume', 'ancil_dir', 'basRunoff', 'case_name', + 'computeReachList', 'debug', 'desireId', 'dlayRunoff', 'dname_data_remap', 'dname_hru_remap', + 'dname_hruid', 'dname_nhru', 'dname_segid_wm', 'dname_sseg', 'dname_time', 'dname_time_wm', + 'dname_xlon', 'dname_ylat', 'doesBasinRoute', 'dt_qsim', 'dt_ro', 'fname_ntopNew', + 'fname_ntopOld', 'fname_qsim', 'fname_remap', 'fname_state_in', 'fname_wm', + 'histTimeStamp_offset', 'hw_drain_point', 'hydGeometryOption', 'input_dir', 'instRunoff', + 'is_flux_wm', 'is_lake_sim', 'is_remap', 'is_vol_wm', 'is_vol_wm_jumpstart', + 'newFileFrequency', 'ntopAugmentMode', 'outputFrequency', 'output_dir', 'param_nml', + 'restart_date', 'restart_write', 'route_opt', 'seg_outlet', 'sim_end', 'sim_start', + 'sumUpstreamRunoff', 'topoNetworkOption', 'units_qsim', 'varname_HRUid', 'varname_LakeTargVol', + 'varname_area', 'varname_downSegId', 'varname_hruSegId', 'varname_islake', 'varname_lakeModelType', + 'varname_length', 'varname_segId', 'varname_slope', 'vname_evapo', 'vname_flux_wm', + 'vname_hruid', 'vname_hruid_in_remap', 'vname_i_index', 'vname_j_index', 'vname_num_qhru', + 'vname_precip', 'vname_qhruid', 'vname_qsim', 'vname_segid_wm', 'vname_time', + 'vname_time_wm', 'vname_vol_wm', 'vname_weight' + } + parsed_keys = set(ctl.get_elmList()) + self.assertEqual( parsed_keys, expected_set_keys, f"Parsed keys do not match expected set. Diff: {parsed_keys ^ expected_set_keys}" ) + + def test_unknown_toml_key_fails( self ): + temp_file = "temp_unknown.toml" + with open(temp_file, "w") as f: + f.write('bogus_unknown_key = "invalid_value"\n') + try: + self.assertRaises( SystemExit, mizuRoute_control.from_toml, temp_file ) + finally: + if os.path.exists(temp_file): + os.remove(temp_file) def test_allow_empty( self ): - self.ctl.read( "../../cime_config/user_nl_mizuRoute", allowEmpty=True ) - self.assertTrue( self.ctl.is_read() ) + self.ctl.read( "../../cime_config/user_nl_mizuRoute", allowEmpty=True ) + self.assertTrue( self.ctl.is_read() ) def test_is_read_coupled( self ): self.assertFalse( self.ctl.is_read() ) - self.ctl.read( "SAMPLE-coupled.control" ) + self.ctl.read( "SAMPLE-coupled_toml" ) self.assertTrue( self.ctl.is_read() ) def test_get_not_read( self ): @@ -203,7 +356,7 @@ def test_bad_file( self ): def test_get_after_set( self ): name = "thingwithlongname" value = "valuereturned" - self.ctl.read( "SAMPLE.control" ) + self.ctl.read( "SAMPLE_toml" ) self.ctl.set( name, value, allowNewName=True ) getvalue = self.ctl.get( name ) self.assertEqual( getvalue, value ) @@ -212,7 +365,7 @@ def test_get_bad_name_after_set( self ): name = "thingwithlongname" name2 = name + "even_longer" value = "valuereturned" - self.ctl.read( "SAMPLE.control" ) + self.ctl.read( "SAMPLE_toml" ) self.ctl.set( name, value, allowNewName=True ) getvalue = self.ctl.get( name2 ) self.assertEqual( getvalue, "UNSET" ) @@ -220,28 +373,111 @@ def test_get_bad_name_after_set( self ): def test_set_doesnot_allow_newname( self ): name = "thingwithlongnamethatsnotonthefile" value = "valuetoset" - self.ctl.read( "SAMPLE.control" ) + self.ctl.read( "SAMPLE_toml" ) self.assertRaises( SystemExit, self.ctl.set, name, value ) - def test_empty_file( self ): self.assertRaises( SystemExit, self.ctl.read, "../../cime_config/user_nl_mizuRoute" ) def test_read_in_two_control_files( self ): - # Read in two control files make sure their list of elements is different - self.ctl.read( "SAMPLE.control" ) + self.ctl.read( "SAMPLE_toml" ) newctl = mizuRoute_control() newctl.read( "../../cime_config/user_nl_mizuRoute", allowEmpty=True ) self.assertEqual( [], newctl.get_elmList() ) def test_write( self ): - infile = "SAMPLE.control" + infile = "SAMPLE_toml" self.ctl.read( infile ) outfile = "mizuRoute_in" self.ctl.write( outfile ) - if ( not run_cmd_no_fail( "diff -wb "+infile+" "+outfile ) == "" ): - expect( False, "Write of input file results in something different" ) + self.assertTrue( os.path.exists(outfile) ) os.remove( outfile ) + def test_read_legacy_control( self ): + legacy_file = "temp_legacy_control" + with open(legacy_file, "w") as f: + f.write(" 5 ! Legacy comment\n") + f.write(" 1 ! Legacy comment\n") + + legacy_ctl = mizuRoute_control.from_control( legacy_file ) + self.assertEqual( legacy_ctl.get("route_opt"), "5" ) + self.assertEqual( legacy_ctl.get("doesAccumRunoff"), "1" ) + os.remove( legacy_file ) + + def test_legacy_reader_fails_on_toml_data( self ): + bad_control_file = "temp_toml_syntax_control" + with open(bad_control_file, "w") as f: + f.write("route_opt = 5\n") + + try: + self.assertRaises( SystemExit, mizuRoute_control.from_control, bad_control_file ) + finally: + if os.path.exists(bad_control_file): + os.remove( bad_control_file ) + + def test_toml_reader_fails_on_legacy_data( self ): + bad_toml_file = "temp_legacy_syntax_toml" + with open(bad_toml_file, "w") as f: + f.write(" 5 ! comment\n") + + try: + self.assertRaises( (SystemExit, Exception), mizuRoute_control.from_toml, bad_toml_file ) + finally: + if os.path.exists(bad_toml_file): + os.remove( bad_toml_file ) + + def test_factory_constructors( self ): + toml_ctl = mizuRoute_control.from_toml( "SAMPLE_toml" ) + self.assertTrue( toml_ctl.is_read() ) + self.assertNotEqual( toml_ctl.get("route_opt"), "UNSET" ) + + legacy_file = "temp_legacy_factory_control" + with open(legacy_file, "w") as f: + f.write(" 3 ! Legacy comment\n") + try: + legacy_ctl = mizuRoute_control.from_control( legacy_file ) + self.assertEqual( legacy_ctl.get("route_opt"), "3" ) + finally: + if os.path.exists(legacy_file): + os.remove( legacy_file ) + +def main(): + import argparse + parser = argparse.ArgumentParser(description="mizuRoute Control / TOML format conversion utility.") + parser.add_argument("--from_toml", type=str, help="Input TOML control file path") + parser.add_argument("--from_control", type=str, help="Input legacy control file path") + parser.add_argument("--to_toml", type=str, help="Output TOML control file path") + parser.add_argument("--to_control", type=str, help="Output legacy control file path") + + if len(sys.argv) == 1 or (len(sys.argv) > 1 and sys.argv[1].startswith("-v")): + unittest.main() + return + + args, unknown = parser.parse_known_args() + + if not args.from_toml and not args.from_control: + unittest.main() + return + + expect(not (args.from_toml and args.from_control), "Specify either --from_toml or --from_control, not both.") + expect(not (args.to_toml and args.to_control), "Specify either --to_toml or --to_control, not both.") + expect(args.to_toml or args.to_control, "Specify output target using either --to_toml= or --to_control=.") + + if args.from_toml: + infile = args.from_toml + ctl = mizuRoute_control.from_toml(infile) + else: + infile = args.from_control + ctl = mizuRoute_control.from_control(infile) + + if args.to_toml: + outfile = args.to_toml + ctl.write(outfile) + else: + outfile = args.to_control + ctl.write_legacy(outfile) + + print(f"Successfully converted {infile} -> {outfile}") + if __name__ == '__main__': - unittest.main() + main()