clm5/cime_config/buildnml
2024-05-09 15:14:01 +08:00

419 lines
15 KiB
Python

#!/usr/bin/env python3
"""
CTSM namelist creator
"""
import sys, os, shutil, re
_CIMEROOT = os.environ.get("CIMEROOT")
if _CIMEROOT is None:
raise SystemExit("ERROR: must set CIMEROOT environment variable")
_LIBDIR = os.path.join(_CIMEROOT, "CIME", "Tools")
sys.path.append(_LIBDIR)
from standard_script_setup import *
from CIME.buildnml import create_namelist_infile, parse_input
from CIME.case import Case
from CIME.utils import expect, run_cmd
logger = logging.getLogger(__name__)
_config_cache_template = """
<?xml version="1.0"?>
<config_definition>
<commandline></commandline>
<entry id="phys" value="{clm_phys}" list="" valid_values="clm4_5,clm5_0,clm5_1,clm6_0">Specifies CTSM physics</entry>
</config_definition>
"""
###############################################################################
def buildnml(case, caseroot, compname):
###############################################################################
"""Build the CTSM namelist"""
# Build the component namelist
if compname != "ctsm" and compname != "clm":
raise AttributeError
lnd_root = case.get_value("COMP_ROOT_DIR_LND")
din_loc_root = case.get_value("DIN_LOC_ROOT")
configuration = case.get_value("CLM_CONFIGURATION")
structure = case.get_value("CLM_STRUCTURE")
ccsm_co2_ppmv = case.get_value("CCSM_CO2_PPMV")
casename = case.get_value("CASE")
clm_co2_type = case.get_value("CLM_CO2_TYPE")
clm_namelist_opts = case.get_value("CLM_NAMELIST_OPTS")
clm_bldnml_opts = case.get_value("CLM_BLDNML_OPTS")
clm_nml_use_case = case.get_value("CLM_NML_USE_CASE")
clm_force_coldstart = case.get_value("CLM_FORCE_COLDSTART")
lnd_tuning_mode = case.get_value("LND_TUNING_MODE")
clm_accelerated_spinup = case.get_value("CLM_ACCELERATED_SPINUP")
comp_atm = case.get_value("COMP_ATM")
lnd_grid = case.get_value("LND_GRID")
ninst_lnd = case.get_value("NINST_LND")
rundir = case.get_value("RUNDIR")
run_type = case.get_value("RUN_TYPE")
run_startdate = case.get_value("RUN_STARTDATE")
run_refcase = case.get_value("RUN_REFCASE")
run_refdate = case.get_value("RUN_REFDATE")
run_reftod = case.get_value("RUN_REFTOD")
glc_nec = case.get_value("GLC_NEC")
glc_use_antarctica = case.get_value("GLC_USE_ANTARCTICA")
mask = case.get_value("MASK_GRID")
driver = case.get_value("COMP_INTERFACE").lower()
# Create init_generated_files directory if not there
newdir = os.path.join(rundir, "init_generated_files")
if not os.path.exists(newdir):
os.mkdir(newdir)
# -----------------------------------------------------
# Error checking
# -----------------------------------------------------
if clm_bldnml_opts.find("-namelist") >= 0:
expect(
False,
"The -namelist option is NOT allowed to be part of CLM_BLDNML_OPTS, "
+ "use the CLM_NAMELIST_OPTS option or add namelist items to user_nl_clm instead ",
)
#
# Warnings for land tuning modes
#
closest_tuning = {
"clm4_5_1PT": "clm4_5_CRUv7",
"clm4_5_QIAN": "clm4_5_CRUv7",
"clm4_5_NLDAS2": "clm4_5_CRUv7",
"clm4_5_ERA5": "clm4_5_CRUv7",
"clm5_0_1PT": "clm5_0_GSWP3v1",
"clm5_0_QIAN": "clm5_0_GSWP3v1",
"clm5_0_NLDAS2": "clm5_0_GSWP3v1",
"clm5_0_ERA5": "clm5_0_GSWP3v1",
"clm5_1_1PT": "clm5_1_GSWP3v1",
"clm5_1_QIAN": "clm5_1_GSWP3v1",
"clm5_1_NLDAS2": "clm5_1_GSWP3v1",
"clm5_1_ERA5": "clm5_1_GSWP3v1",
"clm5_1_CRUv7": "clm5_1_GSWP3v1",
"clm6_0_1PT": "clm6_0_GSWP3v1",
"clm6_0_QIAN": "clm6_0_GSWP3v1",
"clm6_0_NLDAS2": "clm6_0_GSWP3v1",
"clm6_0_ERA5": "clm6_0_GSWP3v1",
"clm6_0_CRUv7": "clm6_0_GSWP3v1",
}
for mode, closest in closest_tuning.items():
if lnd_tuning_mode == mode:
logger.warning(
"IMPORTANT NOTE: LND_TUNING_MODE is "
+ lnd_tuning_mode
+ " which does NOT have tuned settings, so using the closest option which is "
+ closest
)
logger.warning(
" : To suppress this message explicitly set LND_TUNING_MODE="
+ lnd_tuning_mode
+ " for your case"
)
lnd_tuning_mode = closest
# CAM4 and CAM5 options are based on cam6 tuning
# (other than the Zender dust emission soil eroditability file which is specific
# to the CAM version)
tuning_based_on = {
"clm6_0_GSWP3v1": "clm5_0_GSWP3v1",
"clm5_1_GSWP3v1": "clm5_0_GSWP3v1",
"clm6_0_cam6.0": "clm5_0_cam6.0",
"clm6_0_cam5.0": "clm5_0_cam6.0",
"clm6_0_cam4.0": "clm5_0_cam6.0",
"clm5_1_cam6.0": "clm5_0_cam6.0",
"clm5_1_cam5.0": "clm5_0_cam6.0",
"clm5_1_cam4.0": "clm5_0_cam6.0",
"clm5_0_cam5.0": "clm5_0_cam6.0",
"clm5_0_cam4.0": "clm5_0_cam6.0",
"clm4_5_cam6.0": "clm5_0_cam6.0",
"clm4_5_cam5.0": "clm5_0_cam6.0",
"clm4_5_cam4.0": "clm5_0_cam6.0",
}
for mode, based_on in tuning_based_on.items():
if lnd_tuning_mode == mode:
logger.warning(
"NOTE: LND_TUNING_MODE is "
+ lnd_tuning_mode
+ " which is NOT tuned, but is based on "
+ based_on
)
# -----------------------------------------------------
# Set ctsmconf
# -----------------------------------------------------
ctsmconf = os.path.join(caseroot, "Buildconf", compname + "conf")
if not os.path.isdir(ctsmconf):
os.makedirs(ctsmconf)
# -----------------------------------------------------
# Create config_cache.xml file
# -----------------------------------------------------
# Note that build-namelist utilizes the contents of the config_cache.xml file in
# the namelist_defaults.xml file to obtain namelist variables
clm_phys = case.get_value("CLM_PHYSICS_VERSION")
config_cache_text = _config_cache_template.format(clm_phys=clm_phys)
config_cache_path = os.path.join(caseroot, "Buildconf", compname + "conf", "config_cache.xml")
with open(config_cache_path, "w") as config_cache_file:
config_cache_file.write(config_cache_text)
# -----------------------------------------------------
# Determine input arguments into build-namelist
# -----------------------------------------------------
startfile_type = "finidat"
start_type = "default"
if run_type == "hybrid":
start_type = "startup"
elif run_type != "startup":
start_type = run_type
if run_type == "branch":
startfile_type = "nrevsn"
if clm_force_coldstart == "on":
clm_force_coldstart = "off"
logger.warning(
"WARNING: You've turned on CLM_FORCE_COLDSTART for a branch run_type, which is a contradiction, the coldstart will be ignored\n"
+ " turn off CLM_FORCE_COLDSTART, or set RUN_TYPE=hybrid to get rid of this warning"
)
if clm_force_coldstart == "on":
logger.warning("WARNING: CLM is starting up from a cold state")
start_type = "cold"
if lnd_grid == "T31":
lnd_grid = "48x96"
if lnd_grid == "T42":
lnd_grid = "64x128"
if lnd_grid == "T85":
lnd_grid = "128x256"
if lnd_grid == "T341":
lnd_grid = "512x1024"
clmusr = ""
if lnd_grid == "CLM_USRDAT":
clm_usrdat_name = case.get_value("CLM_USRDAT_NAME")
clmusr = " -clm_usr_name %s " % clm_usrdat_name
# Write warning about initial condition data
if "NEON" in clm_usrdat_name and clm_force_coldstart == "off":
if ("_transient" in clm_nml_use_case) and (
re.fullmatch(r"\w\w\w\w\.transient", casename) is None
or clm_usrdat_name is "NEON.PRISM"
):
logger.warning(
"WARNING: Do you have appropriate initial conditions for this simulation?"
+ " Check that the finidat file used in the lnd_in namelist is appropriately spunup for your case"
)
if comp_atm != "datm":
nomeg = "-no-megan"
else:
nomeg = ""
if glc_use_antarctica is None:
# This is the case for compsets with SGLC where the GLC_USE_ANTARCTICA xml
# variable isn't defined
glc_use_antarctica_flag = ""
elif isinstance(glc_use_antarctica, bool):
if glc_use_antarctica:
glc_use_antarctica_flag = "-glc_use_antarctica"
else:
glc_use_antarctica_flag = ""
else:
expect(
False,
"Unexpected value for GLC_USE_ANTARCTICA: {}".format(glc_use_antarctica),
)
if clm_nml_use_case != "UNSET":
usecase = "-use_case %s" % clm_nml_use_case
else:
usecase = ""
if (mask != "null") and (mask != "UNSET"):
gridmask = "-mask %s" % mask
else:
gridmask = ""
start_ymd = run_startdate.replace("-", "")
if ("-01-01" in run_startdate) or ("-09-01" in run_startdate):
ignore = "-ignore_ic_year"
else:
ignore = "-ignore_ic_date"
tuning = "-lnd_tuning_mode %s " % lnd_tuning_mode
spinup = "-clm_accelerated_spinup %s " % clm_accelerated_spinup
infile = os.path.join(ctsmconf, "namelist")
inputdata_file = os.path.join(caseroot, "Buildconf", "ctsm.input_data_list")
if driver == "nuopc":
lndfrac_setting = " "
else:
lnd_domain_path = case.get_value("LND_DOMAIN_PATH")
lnd_domain_file = case.get_value("LND_DOMAIN_FILE")
lndfrac_file = os.path.join(lnd_domain_path, lnd_domain_file)
lndfrac_setting = "-lnd_frac " + lndfrac_file
config_cache_file = os.path.join(caseroot, "Buildconf", compname + "conf", "config_cache.xml")
# -----------------------------------------------------
# Clear out old data
# -----------------------------------------------------
if os.path.exists(inputdata_file):
os.remove(inputdata_file)
# -----------------------------------------------------
# loop over instances
# -----------------------------------------------------
ninst = int(ninst_lnd)
for inst_counter in range(1, ninst + 1):
# determine instance string
inst_string = ""
if ninst > 1:
inst_string = "_" + "%04d" % inst_counter
# If multi-instance case does not have restart file, use
# single-case restart for each instance
rpointer = "rpointer.lnd"
if os.path.isfile(os.path.join(rundir, rpointer)) and (
not os.path.isfile(os.path.join(rundir, rpointer + inst_string))
):
shutil.copy(
os.path.join(rundir, rpointer),
os.path.join(rundir, rpointer + inst_string),
)
# -----------------------------------------------------
# call build-namelist
# -----------------------------------------------------
if run_type == "hybrid" or run_type == "branch":
compnames = ["clm4", "clm5", "clm2"]
for comp in compnames:
clm_startfile = "%s.%s%s.r.%s-%s.nc" % (
run_refcase,
comp,
inst_string,
run_refdate,
run_reftod,
)
if os.path.exists(os.path.join(rundir, clm_startfile)):
break
else:
clm_startfile = "%s.%s.r.%s-%s.nc" % (
run_refcase,
comp,
run_refdate,
run_reftod,
)
if os.path.exists(os.path.join(rundir, clm_startfile)):
logger.warning(
"WARNING: the start file being used for a multi-instance case is a single instance: "
+ clm_startfile
)
break
if not os.path.exists(os.path.join(rundir, clm_startfile)):
logger.warning("WARNING: Could NOT find a start file to use using" + clm_startfile)
clm_icfile = "%s = '%s'" % (startfile_type, clm_startfile)
else:
clm_icfile = ""
infile_lines = []
infile_lines.append(clm_icfile)
user_nl_file = os.path.join(caseroot, "user_nl_clm" + inst_string)
namelist_infile = os.path.join(ctsmconf, "namelist")
create_namelist_infile(case, user_nl_file, namelist_infile, "\n".join(infile_lines))
cmd = os.path.join(lnd_root, "bld", "build-namelist")
command = (
'%s -cimeroot %s -infile %s -csmdata %s -inputdata %s %s -namelist "&clm_inparm start_ymd=%s %s/ " '
"%s %s -res %s %s -clm_start_type %s -envxml_dir %s "
"-configuration %s -structure %s "
"%s -glc_nec %s %s -co2_ppmv %s -co2_type %s -config %s -driver %s "
"%s %s %s %s"
% (
cmd,
_CIMEROOT,
infile,
din_loc_root,
inputdata_file,
ignore,
start_ymd,
clm_namelist_opts,
nomeg,
usecase,
lnd_grid,
clmusr,
start_type,
caseroot,
configuration,
structure,
lndfrac_setting,
glc_nec,
glc_use_antarctica_flag,
ccsm_co2_ppmv,
clm_co2_type,
config_cache_file,
driver,
clm_bldnml_opts,
spinup,
tuning,
gridmask,
)
)
rc, out, err = run_cmd(command, from_dir=ctsmconf)
expect(rc == 0, "Command %s failed rc=%d\nout=%s\nerr=%s" % (cmd, rc, out, err))
if out is not None:
logger.debug(" %s" % out)
if err is not None:
logger.debug(" %s" % err)
# -----------------------------------------------------
# copy resolved namelist to rundir
# -----------------------------------------------------
if os.path.isdir(rundir):
file1 = os.path.join(ctsmconf, "lnd_in")
file2 = os.path.join(rundir, "lnd_in")
if ninst > 1:
file2 += inst_string
logger.debug("CTSM namelist copy: file1 %s file2 %s " % (file1, file2))
shutil.copy(file1, file2)
###############################################################################
def _main_func():
caseroot = parse_input(sys.argv)
with Case(caseroot) as case:
compname = case.get_value("COMP_LND")
logger.warning(
"WARNING: buildnml is being called a s program rather than a subroutine "
+ "as it is expected to be in the CESM context"
)
buildnml(case, caseroot, compname)
if __name__ == "__main__":
_main_func()