import glob
import os
import shutil
import sys
from importlib import resources
from jinja2 import Template
from compass.io import symlink
from compass.job import write_job_script
from compass.model import make_graph_file, run_model
from compass.step import Step
[docs]
class SetUpExperiment(Step):
"""
A step for setting up an ISMIP6 experiment
Attributes
----------
"""
[docs]
def __init__(self, test_case, name, subdir, exp):
"""
Set up a new experiment
Parameters
----------
exp : experiment
"""
self.exp = exp
super().__init__(test_case=test_case, name=name, subdir=subdir)
[docs]
def setup(self): # noqa:C901
print(f" Setting up experiment {self.exp}")
config = self.config
section = config['ismip6_run_ais_2300']
self.ntasks = section.getint('ntasks')
self.min_tasks = self.ntasks
mesh_res = section.getint('mesh_res')
forcing_basepath = section.get('forcing_basepath')
init_cond_path = section.get('init_cond_path')
init_cond_fname = os.path.split(init_cond_path)[-1]
melt_params_path = section.get('melt_params_path')
melt_params_fname = os.path.split(melt_params_path)[-1]
region_mask_path = section.get('region_mask_path')
region_mask_fname = os.path.split(region_mask_path)[-1]
calving_method = section.get('calving_method')
use_face_melting = section.getboolean('use_face_melting')
sea_level_model = section.getboolean('sea_level_model')
if self.exp == 'hist':
exp_fcg = 'ctrlAE'
else:
exp_fcg = self.exp
# Define where to get namelist, streams, yaml, etc. templates
# (in current package)
resource_location = \
'compass.landice.tests.ismip6_run.ismip6_ais_proj2300'
# Define calving method
if calving_method == 'von_mises':
use_vM_calving = True
elif calving_method == 'restore':
use_vM_calving = False
# Figure out if the forcing is in tier1 or tier2 subdir
if 'exp' in self.exp:
if int(self.exp[-2:]) >= 7:
forcing_basepath = os.path.join(forcing_basepath,
'tier2_experiments')
else:
forcing_basepath = os.path.join(forcing_basepath,
'tier1_experiments')
else:
forcing_basepath = os.path.join(forcing_basepath,
'tier1_experiments')
# Copy files we'll need from local paths specified in cfg file
if self.exp == 'hist':
shutil.copy(init_cond_path, self.work_dir)
shutil.copy(melt_params_path, self.work_dir)
shutil.copy(region_mask_path, self.work_dir)
# Find and copy correct forcing files
smb_search_path = os.path.join(
forcing_basepath, exp_fcg,
'*smb*bare*.nc')
fcgFileList = glob.glob(smb_search_path)
if len(fcgFileList) == 1:
smb_path = fcgFileList[0]
smb_fname = os.path.split(smb_path)[-1]
shutil.copy(smb_path, self.work_dir)
else:
sys.exit("ERROR: Did not find exactly one matching SMB file at "
f"{smb_search_path}: {fcgFileList}")
tf_search_path = os.path.join(forcing_basepath, exp_fcg,
'*TF_*.nc')
fcgFileList = glob.glob(tf_search_path)
if len(fcgFileList) == 1:
tf_path = fcgFileList[0]
tf_fname = os.path.split(tf_path)[-1]
shutil.copy(tf_path, self.work_dir)
else:
sys.exit("ERROR: Did not find exactly one matching TF file at "
f"{tf_search_path}: {fcgFileList}")
# copy calving mask files for exp11-14
useCalvingMask = False
if exp_fcg[-2:].isdigit():
exp_num = int(exp_fcg[-2:])
if exp_num >= 11:
mask_search_path = os.path.join(
forcing_basepath, exp_fcg,
'Antarctica_8to30km_ice_shelf_collapse_mask_*.nc')
fcgFileList = glob.glob(mask_search_path)
if len(fcgFileList) == 1:
mask_path = fcgFileList[0]
mask_fname = os.path.split(mask_path)[-1]
shutil.copy(mask_path, self.work_dir)
useCalvingMask = True
else:
sys.exit("ERROR: Did not find exactly one matching "
"calving mask file at "
f"{mask_search_path}: {fcgFileList}")
# Make stream modifications based on files that were determined above
stream_replacements = {'input_file_SMB_forcing': smb_fname,
'input_file_TF_forcing': tf_fname}
if self.exp == 'hist':
stream_replacements['input_file_init_cond'] = init_cond_fname
stream_replacements['input_file_region_mask'] = region_mask_fname
stream_replacements['input_file_melt_params'] = melt_params_fname
else:
stream_replacements['input_file_init_cond'] = \
'USE_RESTART_FILE_INSTEAD'
stream_replacements['input_file_region_mask'] = \
'USE_RESTART_FILE_INSTEAD'
stream_replacements['input_file_melt_params'] = \
'USE_RESTART_FILE_INSTEAD'
if self.exp in ['hist', 'ctrlAE']:
stream_replacements['forcing_interval'] = 'initial_only'
else:
stream_replacements['forcing_interval'] = '0001-00-00_00:00:00'
self.add_streams_file(
resource_location,
'streams.landice.template',
out_name='streams.landice',
template_replacements=stream_replacements)
if useCalvingMask:
mask_stream_replacements = {'input_file_calving_mask_forcing_name':
mask_fname}
self.add_streams_file(
resource_location, 'streams.mask_calving',
out_name='streams.landice',
template_replacements=mask_stream_replacements)
if use_vM_calving:
vM_param_path = section.get('von_mises_parameter_path')
vM_stream_replacements = {'input_file_VM_params': vM_param_path}
self.add_streams_file(
resource_location, 'streams.vM_params',
out_name='streams.landice',
template_replacements=vM_stream_replacements)
if use_face_melting:
self.add_streams_file(
resource_location, 'streams.faceMelting',
out_name='streams.landice')
# Set up namelist and customize as needed
self.add_namelist_file(
resource_location, 'namelist.landice',
out_name='namelist.landice')
# set up pio options because we are not using compass run
pio_stride = section.getint('pio_stride')
assert self.ntasks % pio_stride == 0, \
'pio_stride is not evenly divisble into ntasks'
io_tasks = self.ntasks // pio_stride
options = {'config_pio_stride': f'{pio_stride}',
'config_pio_num_iotasks': f'{io_tasks}'}
self.add_namelist_options(options=options,
out_name='namelist.landice')
if self.exp == 'hist':
options = {'config_do_restart': ".false.",
'config_start_time': "'2000-01-01_00:00:00'",
'config_stop_time': "'2015-01-01_00:00:00'"}
self.add_namelist_options(options=options,
out_name='namelist.landice')
if use_vM_calving:
options = {'config_calving': "'von_Mises_stress'",
'config_restore_calving_front': ".false.",
'config_floating_von_Mises_threshold_stress_source':
"'data'",
'config_grounded_von_Mises_threshold_stress_source':
"'data'"}
self.add_namelist_options(options=options,
out_name='namelist.landice')
# Include facemelting if needed
if use_face_melting:
options = {
'config_front_mass_bal_grounded': "'ismip6'",
'config_use_3d_thermal_forcing_for_face_melt': '.true.'}
self.add_namelist_options(options=options,
out_name='namelist.landice')
if useCalvingMask:
options = {'config_calving': "'none'",
'config_apply_calving_mask': ".true."}
self.add_namelist_options(options=options,
out_name='namelist.landice')
if sea_level_model:
# get config options
slm_input_ice = section.get('slm_input_ice')
slm_input_earth = section.get('slm_input_earth')
slm_earth_structure = section.get('slm_earth_structure')
slm_input_others = section.get('slm_input_others')
nglv = section.getint('nglv')
# complete the full paths to the SLM inputs
slm_input_ice = os.path.join(slm_input_ice,
f'GL{nglv}/ice_noGrIS_GL{nglv}/')
slm_input_others = os.path.join(slm_input_others,
f'GL{nglv}/')
# incorporate the SLM config in the landice namelist
options = {'config_uplift_method': "'sealevelmodel'"}
self.add_namelist_options(options=options,
out_name='namelist.landice')
# change the sealevel namelist
template = Template(resources.read_text
(resource_location,
'namelist.sealevel.template'))
text = template.render(nglv=int(nglv), slm_input_ice=slm_input_ice,
slm_input_earth=slm_input_earth,
slm_earth_structure=slm_earth_structure,
slm_input_others=slm_input_others)
# write out the namelist.sealevel file
file_slm_nl = os.path.join(self.work_dir, 'namelist.sealevel')
with open(file_slm_nl, 'w') as handle:
handle.write(text)
# create SLM output paths
os.makedirs(os.path.join(self.work_dir, 'OUTPUT_SLM/'),
exist_ok='True')
os.makedirs(os.path.join(self.work_dir, 'ICELOAD_SLM/'),
exist_ok='True')
# link in SLM mapping files
# they don't exist yet, but we can create symlinks now
map_dir = os.path.join('..', 'mapping_files')
for map_file in ('mapfile_mali_to_slm.nc',
'mapfile_slm_to_mali.nc'):
os.symlink(os.path.join(map_dir, map_file),
os.path.join(self.work_dir, map_file))
# For all projection runs, symlink the restart file for the
# historical run
# don't symlink restart_timestamp or you'll have a mighty mess
if not self.exp == 'hist':
os.symlink(f"../hist_{mesh_res:02}/rst.2015-01-01.nc",
os.path.join(self.work_dir, 'rst.2015-01-01.nc'))
with open(os.path.join(self.work_dir, "restart_timestamp"),
"w") as text_file:
text_file.write("2015-01-01_00:00:00")
# add the albany_input.yaml file
self.add_input_file(
filename='albany_input.yaml',
package=resource_location,
copy=True)
# create graph file
make_graph_file(mesh_filename=init_cond_path,
graph_filename=os.path.join(self.work_dir,
'graph.info'))
# COMPASS does not create symlinks for the load script in step dirs,
# so use the standard approach for creating that symlink in each
# step dir.
if 'LOAD_COMPASS_ENV' in os.environ:
script_filename = os.environ['LOAD_COMPASS_ENV']
# make a symlink to the script for loading the compass conda env.
symlink(script_filename, os.path.join(self.work_dir,
'load_compass_env.sh'))
# customize job script
self.config.set('job', 'job_name', self.exp)
machine = self.config.get('deploy', 'machine')
pre_run_cmd = ('LOGDIR=previous_logs_`date +"%Y-%m-%d_%H-%M-%S"`;'
'mkdir $LOGDIR; cp log* $LOGDIR; date')
post_run_cmd = "date"
write_job_script(self.config, machine,
target_cores=self.ntasks, min_cores=self.min_tasks,
work_dir=self.work_dir,
pre_run_commands=pre_run_cmd,
post_run_commands=post_run_cmd)
# link in exe
self.add_model_as_input()
[docs]
def run(self):
"""
Run this step of the test case
"""
config = self.config
section = config['ismip6_run_ais_2300']
sea_level_model = section.getboolean('sea_level_model')
if sea_level_model:
# Check if mapping files were generated
map_dir = os.path.join('..', 'mapping_files')
for map_file in ('mapfile_mali_to_slm.nc',
'mapfile_slm_to_mali.nc'):
if not os.path.isfile(os.path.join(map_dir, map_file)):
sys.exit(f"ERROR: 'mapping_files/{map_file}'"
"does not exist in workdir."
"Please run the 'mapping_files' step"
"before proceeding.")
run_model(step=self, namelist='namelist.landice',
streams='streams.landice')